@aphexcms/cms-core 0.1.3 → 0.1.5

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 (280) hide show
  1. package/dist/api/assets.d.ts +48 -0
  2. package/dist/api/assets.d.ts.map +1 -0
  3. package/dist/api/assets.js +52 -0
  4. package/dist/api/client.d.ts +37 -0
  5. package/dist/api/client.d.ts.map +1 -0
  6. package/dist/api/client.js +125 -0
  7. package/dist/api/documents.d.ts +56 -0
  8. package/dist/api/documents.d.ts.map +1 -0
  9. package/dist/api/documents.js +77 -0
  10. package/dist/api/index.d.ts +7 -0
  11. package/dist/api/index.d.ts.map +1 -0
  12. package/dist/api/index.js +5 -0
  13. package/dist/api/organizations.d.ts +101 -0
  14. package/dist/api/organizations.d.ts.map +1 -0
  15. package/dist/api/organizations.js +92 -0
  16. package/dist/api/types.d.ts +23 -0
  17. package/dist/api/types.d.ts.map +1 -0
  18. package/dist/api/types.js +1 -0
  19. package/dist/app.d.ts +19 -0
  20. package/dist/auth/MULTI_TENANCY_PLAN.md +1183 -0
  21. package/dist/auth/auth-errors.d.ts +7 -0
  22. package/dist/auth/auth-errors.d.ts.map +1 -0
  23. package/dist/auth/auth-errors.js +13 -0
  24. package/dist/auth/auth-hooks.d.ts +6 -0
  25. package/dist/auth/auth-hooks.d.ts.map +1 -0
  26. package/dist/auth/auth-hooks.js +108 -0
  27. package/dist/auth/provider.d.ts +17 -0
  28. package/dist/auth/provider.d.ts.map +1 -0
  29. package/dist/auth/provider.js +1 -0
  30. package/dist/client/index.d.ts +24 -0
  31. package/dist/client/index.d.ts.map +1 -0
  32. package/dist/client/index.js +31 -0
  33. package/dist/components/AdminApp.svelte +1077 -0
  34. package/dist/components/AdminApp.svelte.d.ts +24 -0
  35. package/dist/components/AdminApp.svelte.d.ts.map +1 -0
  36. package/dist/components/admin/AdminLayout.svelte +115 -0
  37. package/dist/components/admin/AdminLayout.svelte.d.ts +15 -0
  38. package/dist/components/admin/AdminLayout.svelte.d.ts.map +1 -0
  39. package/dist/components/admin/DocumentEditor.svelte +795 -0
  40. package/dist/components/admin/DocumentEditor.svelte.d.ts +18 -0
  41. package/dist/components/admin/DocumentEditor.svelte.d.ts.map +1 -0
  42. package/dist/components/admin/DocumentTypesList.svelte +97 -0
  43. package/dist/components/admin/DocumentTypesList.svelte.d.ts +14 -0
  44. package/dist/components/admin/DocumentTypesList.svelte.d.ts.map +1 -0
  45. package/dist/components/admin/ObjectModal.svelte +135 -0
  46. package/dist/components/admin/ObjectModal.svelte.d.ts +15 -0
  47. package/dist/components/admin/ObjectModal.svelte.d.ts.map +1 -0
  48. package/dist/components/admin/SchemaField.svelte +171 -0
  49. package/dist/components/admin/SchemaField.svelte.d.ts +19 -0
  50. package/dist/components/admin/SchemaField.svelte.d.ts.map +1 -0
  51. package/dist/components/admin/fields/ArrayField.svelte +266 -0
  52. package/dist/components/admin/fields/ArrayField.svelte.d.ts +12 -0
  53. package/dist/components/admin/fields/ArrayField.svelte.d.ts.map +1 -0
  54. package/dist/components/admin/fields/BooleanField.svelte +35 -0
  55. package/dist/components/admin/fields/BooleanField.svelte.d.ts +13 -0
  56. package/dist/components/admin/fields/BooleanField.svelte.d.ts.map +1 -0
  57. package/dist/components/admin/fields/ImageField.svelte +284 -0
  58. package/dist/components/admin/fields/ImageField.svelte.d.ts +15 -0
  59. package/dist/components/admin/fields/ImageField.svelte.d.ts.map +1 -0
  60. package/dist/components/admin/fields/NumberField.svelte +82 -0
  61. package/dist/components/admin/fields/NumberField.svelte.d.ts +14 -0
  62. package/dist/components/admin/fields/NumberField.svelte.d.ts.map +1 -0
  63. package/dist/components/admin/fields/ReferenceField.svelte +260 -0
  64. package/dist/components/admin/fields/ReferenceField.svelte.d.ts +12 -0
  65. package/dist/components/admin/fields/ReferenceField.svelte.d.ts.map +1 -0
  66. package/dist/components/admin/fields/SlugField.svelte +74 -0
  67. package/dist/components/admin/fields/SlugField.svelte.d.ts +15 -0
  68. package/dist/components/admin/fields/SlugField.svelte.d.ts.map +1 -0
  69. package/dist/components/admin/fields/StringField.svelte +40 -0
  70. package/dist/components/admin/fields/StringField.svelte.d.ts +14 -0
  71. package/dist/components/admin/fields/StringField.svelte.d.ts.map +1 -0
  72. package/dist/components/admin/fields/TextareaField.svelte +40 -0
  73. package/dist/components/admin/fields/TextareaField.svelte.d.ts +14 -0
  74. package/dist/components/admin/fields/TextareaField.svelte.d.ts.map +1 -0
  75. package/dist/components/fields/index.d.ts +9 -0
  76. package/dist/components/fields/index.d.ts.map +1 -0
  77. package/dist/components/fields/index.js +9 -0
  78. package/dist/components/index.d.ts +7 -0
  79. package/dist/components/index.d.ts.map +1 -0
  80. package/dist/components/index.js +12 -0
  81. package/dist/components/layout/OrganizationSwitcher.svelte +218 -0
  82. package/dist/components/layout/OrganizationSwitcher.svelte.d.ts +11 -0
  83. package/dist/components/layout/OrganizationSwitcher.svelte.d.ts.map +1 -0
  84. package/dist/components/layout/Sidebar.svelte +88 -0
  85. package/dist/components/layout/Sidebar.svelte.d.ts +14 -0
  86. package/dist/components/layout/Sidebar.svelte.d.ts.map +1 -0
  87. package/dist/components/layout/sidebar/AppSidebar.svelte +63 -0
  88. package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts +11 -0
  89. package/dist/components/layout/sidebar/AppSidebar.svelte.d.ts.map +1 -0
  90. package/dist/components/layout/sidebar/NavMain.svelte +95 -0
  91. package/dist/components/layout/sidebar/NavMain.svelte.d.ts +19 -0
  92. package/dist/components/layout/sidebar/NavMain.svelte.d.ts.map +1 -0
  93. package/dist/components/layout/sidebar/NavSecondary.svelte +69 -0
  94. package/dist/components/layout/sidebar/NavSecondary.svelte.d.ts +9 -0
  95. package/dist/components/layout/sidebar/NavSecondary.svelte.d.ts.map +1 -0
  96. package/dist/components/layout/sidebar/NavUser.svelte +85 -0
  97. package/dist/components/layout/sidebar/NavUser.svelte.d.ts +9 -0
  98. package/dist/components/layout/sidebar/NavUser.svelte.d.ts.map +1 -0
  99. package/dist/config.d.ts +3 -0
  100. package/dist/config.d.ts.map +1 -0
  101. package/dist/config.js +15 -0
  102. package/dist/db/adapters/index.d.ts +1 -0
  103. package/dist/db/adapters/index.d.ts.map +1 -0
  104. package/dist/db/adapters/index.js +4 -0
  105. package/dist/db/index.d.ts +2 -0
  106. package/dist/db/index.d.ts.map +1 -0
  107. package/dist/db/index.js +4 -0
  108. package/dist/db/interfaces/asset.d.ts +51 -0
  109. package/dist/db/interfaces/asset.d.ts.map +1 -0
  110. package/dist/db/interfaces/asset.js +1 -0
  111. package/dist/db/interfaces/document.d.ts +36 -0
  112. package/dist/db/interfaces/document.d.ts.map +1 -0
  113. package/dist/db/interfaces/document.js +1 -0
  114. package/dist/db/interfaces/index.d.ts +73 -0
  115. package/dist/db/interfaces/index.d.ts.map +1 -0
  116. package/dist/db/interfaces/index.js +1 -0
  117. package/dist/db/interfaces/organization.d.ts +27 -0
  118. package/dist/db/interfaces/organization.d.ts.map +1 -0
  119. package/dist/db/interfaces/organization.js +1 -0
  120. package/dist/db/interfaces/schema.d.ts +21 -0
  121. package/dist/db/interfaces/schema.d.ts.map +1 -0
  122. package/dist/db/interfaces/schema.js +1 -0
  123. package/dist/db/interfaces/user.d.ts +15 -0
  124. package/dist/db/interfaces/user.d.ts.map +1 -0
  125. package/dist/db/interfaces/user.js +1 -0
  126. package/dist/db/utils/reference-resolver.d.ts +18 -0
  127. package/dist/db/utils/reference-resolver.d.ts.map +1 -0
  128. package/dist/db/utils/reference-resolver.js +80 -0
  129. package/dist/define.d.ts +3 -0
  130. package/dist/define.d.ts.map +1 -0
  131. package/dist/define.js +4 -0
  132. package/dist/email/index.d.ts +2 -0
  133. package/dist/email/index.d.ts.map +1 -0
  134. package/dist/email/index.js +4 -0
  135. package/dist/email/interfaces/email.d.ts +42 -0
  136. package/dist/email/interfaces/email.d.ts.map +1 -0
  137. package/dist/email/interfaces/email.js +1 -0
  138. package/dist/engine.d.ts +26 -0
  139. package/dist/engine.d.ts.map +1 -0
  140. package/dist/engine.js +66 -0
  141. package/dist/field-validation/rule.d.ts +51 -0
  142. package/dist/field-validation/rule.d.ts.map +1 -0
  143. package/dist/field-validation/rule.js +221 -0
  144. package/dist/field-validation/utils.d.ts +21 -0
  145. package/dist/field-validation/utils.d.ts.map +1 -0
  146. package/dist/field-validation/utils.js +66 -0
  147. package/dist/hooks.d.ts +23 -0
  148. package/dist/hooks.d.ts.map +1 -0
  149. package/dist/hooks.js +96 -0
  150. package/dist/index.d.ts +2 -0
  151. package/dist/index.d.ts.map +1 -0
  152. package/dist/index.js +4 -0
  153. package/dist/plugins/README.md +154 -0
  154. package/dist/routes/assets-by-id.d.ts +5 -0
  155. package/dist/routes/assets-by-id.d.ts.map +1 -0
  156. package/dist/routes/assets-by-id.js +138 -0
  157. package/dist/routes/assets-cdn.d.ts +3 -0
  158. package/dist/routes/assets-cdn.d.ts.map +1 -0
  159. package/dist/routes/assets-cdn.js +155 -0
  160. package/dist/routes/assets.d.ts +4 -0
  161. package/dist/routes/assets.d.ts.map +1 -0
  162. package/dist/routes/assets.js +94 -0
  163. package/dist/routes/documents-by-id.d.ts +5 -0
  164. package/dist/routes/documents-by-id.d.ts.map +1 -0
  165. package/dist/routes/documents-by-id.js +142 -0
  166. package/dist/routes/documents-publish.d.ts +4 -0
  167. package/dist/routes/documents-publish.d.ts.map +1 -0
  168. package/dist/routes/documents-publish.js +151 -0
  169. package/dist/routes/documents.d.ts +4 -0
  170. package/dist/routes/documents.d.ts.map +1 -0
  171. package/dist/routes/documents.js +131 -0
  172. package/dist/routes/index.d.ts +6 -0
  173. package/dist/routes/index.d.ts.map +1 -0
  174. package/dist/routes/index.js +10 -0
  175. package/dist/routes/organizations-by-id.d.ts +5 -0
  176. package/dist/routes/organizations-by-id.d.ts.map +1 -0
  177. package/dist/routes/organizations-by-id.js +187 -0
  178. package/dist/routes/organizations-invitations.d.ts +4 -0
  179. package/dist/routes/organizations-invitations.d.ts.map +1 -0
  180. package/dist/routes/organizations-invitations.js +125 -0
  181. package/dist/routes/organizations-members.d.ts +5 -0
  182. package/dist/routes/organizations-members.d.ts.map +1 -0
  183. package/dist/routes/organizations-members.js +206 -0
  184. package/dist/routes/organizations-switch.d.ts +3 -0
  185. package/dist/routes/organizations-switch.d.ts.map +1 -0
  186. package/dist/routes/organizations-switch.js +53 -0
  187. package/dist/routes/organizations.d.ts +4 -0
  188. package/dist/routes/organizations.d.ts.map +1 -0
  189. package/dist/routes/organizations.js +108 -0
  190. package/dist/routes/schemas-by-type.d.ts +3 -0
  191. package/dist/routes/schemas-by-type.d.ts.map +1 -0
  192. package/dist/routes/schemas-by-type.js +25 -0
  193. package/dist/routes/schemas.d.ts +3 -0
  194. package/dist/routes/schemas.d.ts.map +1 -0
  195. package/dist/routes/schemas.js +11 -0
  196. package/dist/routes-exports.d.ts +14 -0
  197. package/dist/routes-exports.d.ts.map +1 -0
  198. package/dist/routes-exports.js +19 -0
  199. package/dist/schema-context.svelte.d.ts +10 -0
  200. package/dist/schema-context.svelte.d.ts.map +1 -0
  201. package/dist/schema-context.svelte.js +18 -0
  202. package/dist/schema-utils/cleanup.d.ts +21 -0
  203. package/dist/schema-utils/cleanup.d.ts.map +1 -0
  204. package/dist/schema-utils/cleanup.js +80 -0
  205. package/dist/schema-utils/index.d.ts +4 -0
  206. package/dist/schema-utils/index.d.ts.map +1 -0
  207. package/dist/schema-utils/index.js +4 -0
  208. package/dist/schema-utils/utils.d.ts +30 -0
  209. package/dist/schema-utils/utils.d.ts.map +1 -0
  210. package/dist/schema-utils/utils.js +37 -0
  211. package/dist/schema-utils/validator.d.ts +6 -0
  212. package/dist/schema-utils/validator.d.ts.map +1 -0
  213. package/dist/schema-utils/validator.js +45 -0
  214. package/dist/server/index.d.ts +16 -0
  215. package/dist/server/index.d.ts.map +1 -0
  216. package/dist/server/index.js +28 -0
  217. package/dist/services/asset-service.d.ts +86 -0
  218. package/dist/services/asset-service.d.ts.map +1 -0
  219. package/dist/services/asset-service.js +187 -0
  220. package/dist/services/index.d.ts +3 -0
  221. package/dist/services/index.d.ts.map +1 -0
  222. package/dist/services/index.js +4 -0
  223. package/dist/storage/adapters/index.d.ts +2 -0
  224. package/dist/storage/adapters/index.d.ts.map +1 -0
  225. package/dist/storage/adapters/index.js +2 -0
  226. package/dist/storage/adapters/local-storage-adapter.d.ts +54 -0
  227. package/dist/storage/adapters/local-storage-adapter.d.ts.map +1 -0
  228. package/dist/storage/adapters/local-storage-adapter.js +187 -0
  229. package/dist/storage/index.d.ts +3 -0
  230. package/dist/storage/index.d.ts.map +1 -0
  231. package/dist/storage/index.js +6 -0
  232. package/dist/storage/interfaces/index.d.ts +2 -0
  233. package/dist/storage/interfaces/index.d.ts.map +1 -0
  234. package/dist/storage/interfaces/index.js +2 -0
  235. package/dist/storage/interfaces/storage.d.ts +91 -0
  236. package/dist/storage/interfaces/storage.d.ts.map +1 -0
  237. package/dist/storage/interfaces/storage.js +1 -0
  238. package/dist/storage/providers/storage.d.ts +43 -0
  239. package/dist/storage/providers/storage.d.ts.map +1 -0
  240. package/dist/storage/providers/storage.js +64 -0
  241. package/dist/types/asset.d.ts +73 -0
  242. package/dist/types/asset.d.ts.map +1 -0
  243. package/dist/types/asset.js +2 -0
  244. package/dist/types/auth.d.ts +50 -0
  245. package/dist/types/auth.d.ts.map +1 -0
  246. package/dist/types/auth.js +41 -0
  247. package/dist/types/config.d.ts +47 -0
  248. package/dist/types/config.d.ts.map +1 -0
  249. package/dist/types/config.js +1 -0
  250. package/dist/types/document.d.ts +34 -0
  251. package/dist/types/document.d.ts.map +1 -0
  252. package/dist/types/document.js +1 -0
  253. package/dist/types/index.d.ts +9 -0
  254. package/dist/types/index.d.ts.map +1 -0
  255. package/dist/types/index.js +8 -0
  256. package/dist/types/organization.d.ts +105 -0
  257. package/dist/types/organization.d.ts.map +1 -0
  258. package/dist/types/organization.js +3 -0
  259. package/dist/types/schemas.d.ts +114 -0
  260. package/dist/types/schemas.d.ts.map +1 -0
  261. package/dist/types/schemas.js +1 -0
  262. package/dist/types/sidebar.d.ts +33 -0
  263. package/dist/types/sidebar.d.ts.map +1 -0
  264. package/dist/types/sidebar.js +1 -0
  265. package/dist/types/user.d.ts +14 -0
  266. package/dist/types/user.d.ts.map +1 -0
  267. package/dist/types/user.js +1 -0
  268. package/dist/utils/content-hash.d.ts +22 -0
  269. package/dist/utils/content-hash.d.ts.map +1 -0
  270. package/dist/utils/content-hash.js +67 -0
  271. package/dist/utils/image-url.d.ts +88 -0
  272. package/dist/utils/image-url.d.ts.map +1 -0
  273. package/dist/utils/image-url.js +165 -0
  274. package/dist/utils/index.d.ts +6 -0
  275. package/dist/utils/index.d.ts.map +1 -0
  276. package/dist/utils/index.js +9 -0
  277. package/dist/utils/slug.d.ts +13 -0
  278. package/dist/utils/slug.d.ts.map +1 -0
  279. package/dist/utils/slug.js +30 -0
  280. package/package.json +11 -41
@@ -0,0 +1,154 @@
1
+ # Plugin Architecture: A Sanity-like "Parts" System
2
+
3
+ This document outlines a vision for a highly extensible, decoupled plugin architecture for Aphex CMS, inspired by the "parts" system used in Sanity Studio.
4
+
5
+ ## Core Concept: The "Parts" System
6
+
7
+ The goal is to move away from a specialized plugin system, where the core application knows about specific plugin properties like `routes` or `adminUI`, to a generic one.
8
+
9
+ The core concept is a **"parts" system**. A "part" is a formally defined, named extension point in the application that a plugin can provide an implementation for. The core application doesn't know what a "GraphQL Plugin" is; it only knows how to find and render a "tool" in the admin bar, or how to register a "server route".
10
+
11
+ ---
12
+
13
+ ## The Plugin Contract
14
+
15
+ We would redefine the `CMSPlugin` interface to be centered around this "parts" concept.
16
+
17
+ ```typescript
18
+ /**
19
+ * A generic definition for any piece of functionality a plugin can provide.
20
+ */
21
+ export interface PluginPart {
22
+ /**
23
+ * The name of the part this plugin implements.
24
+ * @example 'aphex/admin/tool', 'aphex/server/route'
25
+ */
26
+ implements: string;
27
+
28
+ /** The actual implementation (can be a component, a function, etc.) */
29
+ component?: any; // In practice, a SvelteComponent constructor
30
+
31
+ /** The handler function for a route part */
32
+ handler?: (event: import('@sveltejs/kit').RequestEvent) => Response | Promise<Response>;
33
+
34
+ /** Other metadata the part might need for rendering or execution */
35
+ [key: string]: any;
36
+ }
37
+
38
+ /**
39
+ * The new plugin contract. A plugin is a collection of parts.
40
+ */
41
+ export interface CMSPlugin {
42
+ name: string;
43
+ version: string;
44
+ parts?: PluginPart[];
45
+ /** `install` can still be used for complex, one-time setup logic */
46
+ install?: (cms: any) => Promise<void>;
47
+ }
48
+ ```
49
+
50
+ ---
51
+
52
+ ## Defining Core Parts
53
+
54
+ We would define a set of core "parts" for Aphex CMS. This list can grow over time as more extension points are needed.
55
+
56
+ - `aphex/server/route`: Implemented by plugins that need to add a server-side API endpoint. The part would include a `path` and a `handler`.
57
+ - `aphex/admin/tool`: For adding a top-level, navigable tool (like a tab) to the main admin UI. The part would include an `id`, `title`, and a `component` to render.
58
+ - `aphex/field/component`: For registering a custom Svelte component to use for a specific schema field type (e.g., a special string input, a map selector, etc.).
59
+ - `aphex/document/action`: For adding a custom action button to the document editor (e.g., "Duplicate", "Translate", "Preview").
60
+
61
+ ---
62
+
63
+ ## Implementation with SvelteKit and Svelte 5
64
+
65
+ This architecture fits beautifully with SvelteKit's and Svelte 5's features.
66
+
67
+ ### The Part Resolver Service
68
+
69
+ A singleton service, the "Part Resolver," would be the heart of the system.
70
+
71
+ 1. **Initialization:** At application startup, the main `hooks.server.ts` would process the `cmsConfig.plugins` array once. It would loop through every plugin and every part, organizing them into a `Map<string, PluginPart[]>`. For example, the key `'aphex/admin/tool'` would hold an array of all tool parts from all installed plugins.
72
+ 2. **Injection:** This resolver instance would be attached to SvelteKit's `event.locals`, making it universally available in `load` functions, server-side hooks, and API routes without needing global variables.
73
+
74
+ ### Rendering UI Parts with Svelte 5
75
+
76
+ Dynamically rendering plugin components becomes trivial and efficient with Svelte 5.
77
+
78
+ 1. **Data Loading:** In the `+page.server.ts` for the admin UI, the `load` function would use the resolver from `locals` to fetch the necessary parts.
79
+ ```typescript
80
+ // in /routes/admin/+page.server.ts
81
+ export async function load({ locals }) {
82
+ const { partResolver } = locals.aphexCMS;
83
+ const adminTools = partResolver.getParts('aphex/admin/tool');
84
+ return { adminTools };
85
+ }
86
+ ```
87
+ 2. **Dynamic Rendering:** The `AdminApp.svelte` component would receive `adminTools` as a prop. It can then dynamically render the tabs and their content using `<svelte:component>`.
88
+
89
+ ```svelte
90
+ <!-- AdminApp.svelte -->
91
+ <script lang="ts">
92
+ let { adminTools = [] } = $props();
93
+ </script>
94
+
95
+ <Tabs.Root>
96
+ <Tabs.List>
97
+ <!-- Render a trigger for each tool -->
98
+ {#each adminTools as tool}
99
+ <Tabs.Trigger value={tool.id}>{tool.title}</Tabs.Trigger>
100
+ {/each}
101
+ </Tabs.List>
102
+
103
+ <!-- Render the content for each tool -->
104
+ {#each adminTools as tool}
105
+ <Tabs.Content value={tool.id}>
106
+ <svelte:component this={tool.component} />
107
+ </Tabs.Content>
108
+ {/each}
109
+ </Tabs.Root>
110
+ ```
111
+
112
+ ### Handling Server Parts in SvelteKit
113
+
114
+ The main `hooks.server.ts` would use the resolver to handle non-UI parts. For example, it would get all `aphex/server/route` parts and register their handlers in a dynamic routing map, similar to how `pluginRoutes` works now but in a fully generic way.
115
+
116
+ ---
117
+
118
+ ## Example: The GraphQL Plugin Revisited
119
+
120
+ Under this system, the GraphQL plugin becomes a clean, declarative manifest of its parts.
121
+
122
+ ```typescript
123
+ // In packages/graphql-plugin/src/index.ts
124
+ import GraphQLTabComponent from './GraphQLTab.svelte';
125
+ import { createYoga } from 'graphql-yoga';
126
+
127
+ // ... (yoga setup)
128
+
129
+ export function createGraphQLPlugin(config: GraphQLPluginConfig = {}): CMSPlugin {
130
+ const endpoint = config.endpoint ?? '/api/graphql';
131
+ const yogaApp = /* ... create yoga instance ... */;
132
+
133
+ return {
134
+ name: '@aphexcms/graphql-plugin',
135
+ parts: [
136
+ // Part 1: The API endpoint
137
+ {
138
+ implements: 'aphex/server/route',
139
+ path: endpoint,
140
+ handler: (event) => yogaApp.fetch(event.request, event)
141
+ },
142
+ // Part 2: The UI tab in the admin interface
143
+ {
144
+ implements: 'aphex/admin/tool',
145
+ id: 'graphql',
146
+ title: 'GraphQL',
147
+ component: GraphQLTabComponent
148
+ }
149
+ ]
150
+ };
151
+ }
152
+ ```
153
+
154
+ With this, the core system remains completely agnostic. It doesn't know what "GraphQL" is, it simply knows how to render a "tool" and route a "server route". This is the foundation of a truly configurable and powerful plugin architecture.
@@ -0,0 +1,5 @@
1
+ import type { RequestHandler } from '@sveltejs/kit';
2
+ export declare const GET: RequestHandler;
3
+ export declare const DELETE: RequestHandler;
4
+ export declare const PATCH: RequestHandler;
5
+ //# sourceMappingURL=assets-by-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assets-by-id.d.ts","sourceRoot":"","sources":["../../src/lib/routes/assets-by-id.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAGpD,eAAO,MAAM,GAAG,EAAE,cAoDjB,CAAC;AAEF,eAAO,MAAM,MAAM,EAAE,cA4BpB,CAAC;AAEF,eAAO,MAAM,KAAK,EAAE,cAwEnB,CAAC"}
@@ -0,0 +1,138 @@
1
+ import { json } from '@sveltejs/kit';
2
+ export const GET = async ({ params, locals }) => {
3
+ try {
4
+ const { assetService } = locals.aphexCMS;
5
+ const auth = locals.auth;
6
+ const { id } = params;
7
+ if (!auth) {
8
+ return json({ success: false, error: 'Unauthorized' }, { status: 401 });
9
+ }
10
+ if (!id) {
11
+ return json({ success: false, error: 'Asset ID is required' }, { status: 400 });
12
+ }
13
+ const asset = await assetService.findAssetById(auth.organizationId, id);
14
+ if (!asset) {
15
+ return json({ success: false, error: 'Asset not found' }, { status: 404 });
16
+ }
17
+ // Return JSON metadata for admin UI
18
+ return json({
19
+ success: true,
20
+ data: {
21
+ _type: asset.assetType === 'image' ? 'sanity.imageAsset' : 'sanity.fileAsset',
22
+ _id: asset.id,
23
+ url: asset.url,
24
+ originalFilename: asset.originalFilename,
25
+ mimeType: asset.mimeType,
26
+ size: asset.size,
27
+ metadata: {
28
+ dimensions: asset.width && asset.height
29
+ ? {
30
+ width: asset.width,
31
+ height: asset.height
32
+ }
33
+ : undefined,
34
+ ...asset.metadata
35
+ },
36
+ title: asset.title,
37
+ description: asset.description,
38
+ alt: asset.alt,
39
+ creditLine: asset.creditLine,
40
+ _createdAt: asset.createdAt,
41
+ _updatedAt: asset.updatedAt
42
+ }
43
+ });
44
+ }
45
+ catch (error) {
46
+ console.error('[Asset API] Error fetching asset:', error);
47
+ return json({ success: false, error: 'Failed to fetch asset' }, { status: 500 });
48
+ }
49
+ };
50
+ export const DELETE = async ({ params, locals }) => {
51
+ try {
52
+ const { id } = params;
53
+ const { assetService } = locals.aphexCMS;
54
+ const auth = locals.auth;
55
+ if (!auth) {
56
+ return json({ success: false, error: 'Unauthorized' }, { status: 401 });
57
+ }
58
+ if (!id) {
59
+ return json({ success: false, error: 'Asset ID is required' }, { status: 400 });
60
+ }
61
+ const result = await assetService.deleteAsset(auth.organizationId, id);
62
+ if (!result) {
63
+ return json({ success: false, error: 'Asset not found or could not be deleted' }, { status: 404 });
64
+ }
65
+ return json({ success: true });
66
+ }
67
+ catch (error) {
68
+ console.error('Error deleting asset:', error);
69
+ return json({ success: false, error: 'Failed to delete asset' }, { status: 500 });
70
+ }
71
+ };
72
+ export const PATCH = async ({ params, locals, request }) => {
73
+ try {
74
+ const { assetService } = locals.aphexCMS;
75
+ const auth = locals.auth;
76
+ const { id } = params;
77
+ if (!auth) {
78
+ return json({ success: false, error: 'Unauthorized' }, { status: 401 });
79
+ }
80
+ if (!id) {
81
+ return json({ success: false, error: 'Asset ID is required' }, { status: 400 });
82
+ }
83
+ const { title, description, alt, creditLine } = await request.json();
84
+ let updatedAsset;
85
+ if (auth.type == 'session') {
86
+ updatedAsset = await assetService.updateAssetMetadata(auth.organizationId, id, {
87
+ title,
88
+ description,
89
+ alt,
90
+ creditLine,
91
+ updatedBy: auth.user.id
92
+ });
93
+ }
94
+ else {
95
+ updatedAsset = await assetService.updateAssetMetadata(auth.organizationId, id, {
96
+ title,
97
+ description,
98
+ alt,
99
+ creditLine,
100
+ updatedBy: auth.keyId
101
+ });
102
+ }
103
+ if (!updatedAsset) {
104
+ return json({ success: false, error: 'Asset not found' }, { status: 404 });
105
+ }
106
+ // Return API response with success wrapper
107
+ return json({
108
+ success: true,
109
+ data: {
110
+ _type: updatedAsset.assetType === 'image' ? 'sanity.imageAsset' : 'sanity.fileAsset',
111
+ _id: updatedAsset.id,
112
+ url: updatedAsset.url,
113
+ originalFilename: updatedAsset.originalFilename,
114
+ mimeType: updatedAsset.mimeType,
115
+ size: updatedAsset.size,
116
+ metadata: {
117
+ dimensions: updatedAsset.width && updatedAsset.height
118
+ ? {
119
+ width: updatedAsset.width,
120
+ height: updatedAsset.height
121
+ }
122
+ : undefined,
123
+ ...updatedAsset.metadata
124
+ },
125
+ title: updatedAsset.title,
126
+ description: updatedAsset.description,
127
+ alt: updatedAsset.alt,
128
+ creditLine: updatedAsset.creditLine,
129
+ _createdAt: updatedAsset.createdAt,
130
+ _updatedAt: updatedAsset.updatedAt
131
+ }
132
+ });
133
+ }
134
+ catch (error) {
135
+ console.error('Error updating asset:', error);
136
+ return json({ success: false, error: 'Failed to update asset' }, { status: 500 });
137
+ }
138
+ };
@@ -0,0 +1,3 @@
1
+ import type { RequestHandler } from '@sveltejs/kit';
2
+ export declare const GET: RequestHandler;
3
+ //# sourceMappingURL=assets-cdn.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assets-cdn.d.ts","sourceRoot":"","sources":["../../src/lib/routes/assets-cdn.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,eAAO,MAAM,GAAG,EAAE,cAsLjB,CAAC"}
@@ -0,0 +1,155 @@
1
+ export const GET = async ({ params, locals, setHeaders, request }) => {
2
+ console.log('[Asset CDN] ========== ROUTE HIT ==========');
3
+ console.log('[Asset CDN] Params:', params);
4
+ try {
5
+ const { assetService, databaseAdapter, storageAdapter, cmsEngine, config } = locals.aphexCMS;
6
+ let auth = locals.auth;
7
+ const { id, filename } = params;
8
+ console.log('[Asset CDN] Request for asset:', id, filename);
9
+ console.log('[Asset CDN] Has auth?', !!auth);
10
+ // If no session auth, check for API key in headers
11
+ if (!auth) {
12
+ const apiKey = request.headers.get('x-api-key');
13
+ console.log('[Asset CDN] API key present?', !!apiKey);
14
+ if (apiKey && config.auth?.provider) {
15
+ try {
16
+ const apiKeyAuth = await config.auth.provider.validateApiKey(request, databaseAdapter);
17
+ if (apiKeyAuth) {
18
+ auth = apiKeyAuth;
19
+ console.log('[Asset CDN] Authenticated via API key, org:', apiKeyAuth.organizationId);
20
+ }
21
+ }
22
+ catch (err) {
23
+ console.warn('[Asset CDN] API key validation failed:', err);
24
+ }
25
+ }
26
+ }
27
+ console.log('[Asset CDN] Auth type:', auth?.type);
28
+ if (!id) {
29
+ return new Response('Asset ID is required', { status: 400 });
30
+ }
31
+ // Try to fetch asset globally first (bypasses RLS for public assets)
32
+ const asset = await assetService.findAssetByIdGlobal(id);
33
+ console.log('[Asset CDN] Asset found globally?', !!asset);
34
+ if (!asset) {
35
+ console.warn('[Asset CDN] Asset not found:', id);
36
+ return new Response('Asset not found', { status: 404 });
37
+ }
38
+ const organizationId = auth?.organizationId;
39
+ console.log('[Asset CDN] Auth object:', JSON.stringify(auth, null, 2));
40
+ console.log('[Asset CDN] Auth organizationId:', organizationId);
41
+ console.log('[Asset CDN] Asset organizationId:', asset.organizationId);
42
+ // Check if this asset is used in a private field
43
+ // The field metadata (schemaType and fieldPath) is stored when the asset is uploaded
44
+ let isPrivate = false;
45
+ const schemaType = asset.metadata?.schemaType;
46
+ const fieldPath = asset.metadata?.fieldPath;
47
+ if (schemaType && fieldPath) {
48
+ // Get the schema definition from IN-MEMORY config (always up-to-date with code changes)
49
+ const schema = cmsEngine.getSchemaTypeByName(schemaType);
50
+ console.log(`[Asset CDN] Schema lookup for ${schemaType}:`, {
51
+ found: !!schema,
52
+ fieldCount: schema?.fields?.length
53
+ });
54
+ if (schema && schema.fields) {
55
+ // Navigate the field path to find the field definition
56
+ const findField = (fields, path) => {
57
+ const parts = path.split('.');
58
+ let current = null;
59
+ for (let i = 0; i < parts.length; i++) {
60
+ const part = parts[i];
61
+ current = fields.find((f) => f.name === part);
62
+ if (!current)
63
+ return null;
64
+ // If not the last part, navigate into nested fields
65
+ if (i < parts.length - 1) {
66
+ if (current.type === 'object' && current.fields) {
67
+ fields = current.fields;
68
+ }
69
+ else {
70
+ return null;
71
+ }
72
+ }
73
+ }
74
+ return current;
75
+ };
76
+ const field = findField(schema.fields, fieldPath);
77
+ console.log(`[Asset CDN] Field lookup for ${fieldPath}:`, {
78
+ found: !!field,
79
+ type: field?.type,
80
+ private: field?.private
81
+ });
82
+ if (field && field.type === 'image') {
83
+ isPrivate = field.private === true;
84
+ console.log(`[Asset CDN] Field check: ${schemaType}.${fieldPath} - private: ${isPrivate}`);
85
+ }
86
+ else {
87
+ console.warn(`[Asset CDN] Could not find field: ${schemaType}.${fieldPath}`);
88
+ }
89
+ }
90
+ }
91
+ else {
92
+ console.log('[Asset CDN] No field metadata - treating as public');
93
+ }
94
+ console.log('[Asset CDN] Asset privacy result:', { isPrivate, schemaType, fieldPath });
95
+ // If asset is private, require auth
96
+ if (isPrivate && !organizationId) {
97
+ console.warn('[Asset CDN] Private asset accessed without auth - DENIED');
98
+ return new Response('Unauthorized - This asset is private', { status: 401 });
99
+ }
100
+ // If asset is private, verify org matches
101
+ if (isPrivate && organizationId && organizationId !== asset.organizationId) {
102
+ console.warn('[Asset CDN] Org mismatch for private asset - FORBIDDEN');
103
+ return new Response('Forbidden', { status: 403 });
104
+ }
105
+ // Log the decision
106
+ if (isPrivate && organizationId) {
107
+ console.log('[Asset CDN] Private asset access ALLOWED - user has auth and org matches');
108
+ }
109
+ else if (!isPrivate) {
110
+ console.log('[Asset CDN] Public asset access ALLOWED');
111
+ }
112
+ console.log('[Asset CDN] Asset found:', {
113
+ id: asset.id,
114
+ path: asset.path,
115
+ mimeType: asset.mimeType,
116
+ storageAdapter: asset.storageAdapter
117
+ });
118
+ // If asset has a direct URL (S3/R2), redirect to it
119
+ if (asset.url && asset.url.startsWith('http')) {
120
+ console.log('[Asset CDN] Redirecting to external URL:', asset.url);
121
+ return new Response(null, {
122
+ status: 302,
123
+ headers: { Location: asset.url }
124
+ });
125
+ }
126
+ // Otherwise, serve from local storage
127
+ if (!storageAdapter?.getObject) {
128
+ console.error('[Asset CDN] Storage adapter does not support getObject');
129
+ return new Response('Storage adapter does not support file serving', { status: 500 });
130
+ }
131
+ console.log('[Asset CDN] Reading file from storage:', asset.path);
132
+ const fileBuffer = await storageAdapter.getObject(asset.path);
133
+ console.log('[Asset CDN] Serving file:', {
134
+ size: fileBuffer.length,
135
+ mimeType: asset.mimeType
136
+ });
137
+ // Set appropriate headers for the asset
138
+ setHeaders({
139
+ 'Content-Type': asset.mimeType || 'application/octet-stream',
140
+ 'Content-Length': fileBuffer.length.toString(),
141
+ 'Cache-Control': 'public, max-age=31536000, immutable', // Cache for 1 year
142
+ 'Content-Disposition': `inline; filename="${encodeURIComponent(asset.originalFilename || asset.filename)}"`,
143
+ ...(asset.mimeType?.startsWith('image/') && {
144
+ 'Accept-Ranges': 'bytes'
145
+ })
146
+ });
147
+ // Convert Buffer to ArrayBuffer for Response
148
+ const arrayBuffer = fileBuffer.buffer.slice(fileBuffer.byteOffset, fileBuffer.byteOffset + fileBuffer.byteLength);
149
+ return new Response(arrayBuffer);
150
+ }
151
+ catch (error) {
152
+ console.error('[Asset CDN] Error serving asset:', error);
153
+ return new Response('Failed to serve asset', { status: 500 });
154
+ }
155
+ };
@@ -0,0 +1,4 @@
1
+ import type { RequestHandler } from '@sveltejs/kit';
2
+ export declare const POST: RequestHandler;
3
+ export declare const GET: RequestHandler;
4
+ //# sourceMappingURL=assets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"assets.d.ts","sourceRoot":"","sources":["../../src/lib/routes/assets.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,eAAO,MAAM,IAAI,EAAE,cAiElB,CAAC;AAEF,eAAO,MAAM,GAAG,EAAE,cA4CjB,CAAC"}
@@ -0,0 +1,94 @@
1
+ // Aphex CMS Asset API Handlers
2
+ import { json } from '@sveltejs/kit';
3
+ export const POST = async ({ request, locals }) => {
4
+ try {
5
+ const { assetService } = locals.aphexCMS;
6
+ const auth = locals.auth;
7
+ if (!auth) {
8
+ return json({ success: false, error: 'Unauthorized' }, { status: 401 });
9
+ }
10
+ const formData = await request.formData();
11
+ const file = formData.get('file');
12
+ if (!file) {
13
+ return json({ success: false, error: 'No file provided' }, { status: 400 });
14
+ }
15
+ // Convert file to buffer
16
+ const arrayBuffer = await file.arrayBuffer();
17
+ const buffer = Buffer.from(arrayBuffer);
18
+ // Get optional metadata from form data
19
+ const title = formData.get('title') || undefined;
20
+ const description = formData.get('description') || undefined;
21
+ const alt = formData.get('alt') || undefined;
22
+ const creditLine = formData.get('creditLine') || undefined;
23
+ // Get field metadata for privacy checking
24
+ const schemaType = formData.get('schemaType') || undefined;
25
+ const fieldPath = formData.get('fieldPath') || undefined;
26
+ // Create asset upload data
27
+ const uploadData = {
28
+ buffer,
29
+ originalFilename: file.name,
30
+ mimeType: file.type,
31
+ size: file.size,
32
+ title,
33
+ description,
34
+ alt,
35
+ creditLine,
36
+ createdBy: auth.type === 'session' ? auth.user.id : undefined,
37
+ metadata: {
38
+ schemaType,
39
+ fieldPath
40
+ }
41
+ };
42
+ // Upload asset using the service
43
+ const asset = await assetService.uploadAsset(auth.organizationId, uploadData);
44
+ return json({
45
+ success: true,
46
+ data: asset
47
+ });
48
+ }
49
+ catch (error) {
50
+ console.error('Asset upload failed:', error);
51
+ return json({
52
+ success: false,
53
+ error: 'Asset upload failed',
54
+ message: error instanceof Error ? error.message : 'Unknown error'
55
+ }, { status: 500 });
56
+ }
57
+ };
58
+ export const GET = async ({ url, locals }) => {
59
+ try {
60
+ const { assetService } = locals.aphexCMS;
61
+ const auth = locals.auth;
62
+ if (!auth) {
63
+ return json({ success: false, error: 'Unauthorized' }, { status: 401 });
64
+ }
65
+ // Parse query parameters
66
+ const assetType = url.searchParams.get('assetType');
67
+ const mimeType = url.searchParams.get('mimeType') || undefined;
68
+ const search = url.searchParams.get('search') || undefined;
69
+ const limitParam = url.searchParams.get('limit');
70
+ const offsetParam = url.searchParams.get('offset');
71
+ const limit = limitParam ? parseInt(limitParam) : 20;
72
+ const offset = offsetParam ? parseInt(offsetParam) : 0;
73
+ const filters = {
74
+ assetType,
75
+ mimeType,
76
+ search,
77
+ limit: isNaN(limit) ? 20 : limit,
78
+ offset: isNaN(offset) ? 0 : offset
79
+ };
80
+ const assets = await assetService.findAssets(auth.organizationId, filters);
81
+ return json({
82
+ success: true,
83
+ data: assets
84
+ });
85
+ }
86
+ catch (error) {
87
+ console.error('Failed to fetch assets:', error);
88
+ return json({
89
+ success: false,
90
+ error: 'Failed to fetch assets',
91
+ message: error instanceof Error ? error.message : 'Unknown error'
92
+ }, { status: 500 });
93
+ }
94
+ };
@@ -0,0 +1,5 @@
1
+ import type { RequestHandler } from '@sveltejs/kit';
2
+ export declare const GET: RequestHandler;
3
+ export declare const PUT: RequestHandler;
4
+ export declare const DELETE: RequestHandler;
5
+ //# sourceMappingURL=documents-by-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"documents-by-id.d.ts","sourceRoot":"","sources":["../../src/lib/routes/documents-by-id.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAKpD,eAAO,MAAM,GAAG,EAAE,cA2DjB,CAAC;AAGF,eAAO,MAAM,GAAG,EAAE,cAoEjB,CAAC;AAGF,eAAO,MAAM,MAAM,EAAE,cA+CpB,CAAC"}