@ekrist1/vulse 0.1.6-alpha.3 → 0.1.7-alpha.4

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 (264) hide show
  1. package/dist/cli/migrate.d.ts.map +1 -1
  2. package/dist/cli/migrate.js +3 -4
  3. package/dist/cli/setup.d.ts.map +1 -1
  4. package/dist/cli/setup.js +11 -10
  5. package/dist/core/blueprints/compile.d.ts +1 -1
  6. package/dist/core/blueprints/compile.d.ts.map +1 -1
  7. package/dist/core/blueprints/compile.js +3 -3
  8. package/dist/core/blueprints/mutations.js +2 -2
  9. package/dist/core/forms/rate-limit.d.ts +1 -1
  10. package/dist/core/forms/rate-limit.d.ts.map +1 -1
  11. package/dist/core/forms/rate-limit.js +3 -3
  12. package/dist/core/forms/unique.d.ts +1 -1
  13. package/dist/core/forms/unique.d.ts.map +1 -1
  14. package/dist/core/forms/unique.js +4 -4
  15. package/dist/core/globals/compile.d.ts +1 -1
  16. package/dist/core/globals/compile.d.ts.map +1 -1
  17. package/dist/core/globals/compile.js +2 -2
  18. package/dist/core/globals/definition.d.ts +1 -1
  19. package/dist/core/globals/definition.d.ts.map +1 -1
  20. package/dist/core/globals/definition.js +3 -3
  21. package/dist/core/repos/globals.js +3 -3
  22. package/dist/core/sha256.d.ts +3 -0
  23. package/dist/core/sha256.d.ts.map +1 -0
  24. package/dist/core/sha256.js +9 -0
  25. package/dist/integration/index.js +1 -1
  26. package/dist/integration/install-hook.d.ts.map +1 -1
  27. package/dist/integration/install-hook.js +6 -4
  28. package/dist/integration/wrangler-config.d.ts +12 -0
  29. package/dist/integration/wrangler-config.d.ts.map +1 -0
  30. package/dist/integration/wrangler-config.js +97 -0
  31. package/dist/integration/wrangler-patch.d.ts +1 -0
  32. package/dist/integration/wrangler-patch.d.ts.map +1 -1
  33. package/dist/integration/wrangler-patch.js +2 -1
  34. package/dist/server/routes/form-submit.js +1 -1
  35. package/dist/version.d.ts +1 -1
  36. package/dist/version.js +1 -1
  37. package/package.json +11 -3
  38. package/src/admin/assets/logo-mark.svg +5 -0
  39. package/src/admin/client/active-locale.ts +17 -0
  40. package/src/admin/client/api.ts +21 -0
  41. package/src/admin/client/form-from-zod.ts +7 -0
  42. package/src/admin/client/live-preview-enabled.ts +5 -0
  43. package/src/admin/components/AdminShell.astro +45 -0
  44. package/src/admin/components/AuthSettings.vue +60 -0
  45. package/src/admin/components/BlockEditor.vue +53 -0
  46. package/src/admin/components/BlueprintEditor.vue +1783 -0
  47. package/src/admin/components/CollectionKindIcon.vue +26 -0
  48. package/src/admin/components/CollectionTree.vue +220 -0
  49. package/src/admin/components/EntryEditorWithPreview.vue +130 -0
  50. package/src/admin/components/EntryForm.vue +411 -0
  51. package/src/admin/components/EntryList.vue +121 -0
  52. package/src/admin/components/EntryStatusBadge.vue +24 -0
  53. package/src/admin/components/FormEditor.vue +233 -0
  54. package/src/admin/components/FormList.vue +54 -0
  55. package/src/admin/components/GlobalSetEditor.vue +272 -0
  56. package/src/admin/components/GlobalSetList.vue +55 -0
  57. package/src/admin/components/LivePreviewPanel.vue +171 -0
  58. package/src/admin/components/LoginForm.vue +53 -0
  59. package/src/admin/components/MediaLibrary.vue +106 -0
  60. package/src/admin/components/MediaPicker.vue +49 -0
  61. package/src/admin/components/RevisionDiff.vue +11 -0
  62. package/src/admin/components/RevisionList.vue +134 -0
  63. package/src/admin/components/SeoFields.vue +113 -0
  64. package/src/admin/components/SetEditor.vue +137 -0
  65. package/src/admin/components/SetList.vue +32 -0
  66. package/src/admin/components/SettingsForm.vue +189 -0
  67. package/src/admin/components/SideNav.vue +152 -0
  68. package/src/admin/components/SubmissionDetail.vue +45 -0
  69. package/src/admin/components/SubmissionList.vue +89 -0
  70. package/src/admin/components/ToastHost.vue +33 -0
  71. package/src/admin/components/TreeRow.vue +163 -0
  72. package/src/admin/components/UserEditor.vue +186 -0
  73. package/src/admin/components/UserList.vue +46 -0
  74. package/src/admin/components/blocks/BlockItem.vue +32 -0
  75. package/src/admin/components/blocks/BlockToolbar.vue +12 -0
  76. package/src/admin/components/blocks/edit/CodeEdit.vue +18 -0
  77. package/src/admin/components/blocks/edit/EmbedEdit.vue +14 -0
  78. package/src/admin/components/blocks/edit/HeadingEdit.vue +19 -0
  79. package/src/admin/components/blocks/edit/ImageEdit.vue +40 -0
  80. package/src/admin/components/blocks/edit/ListEdit.vue +36 -0
  81. package/src/admin/components/blocks/edit/ParagraphEdit.vue +14 -0
  82. package/src/admin/components/blocks/edit/QuoteEdit.vue +18 -0
  83. package/src/admin/components/fields/BlocksField.vue +123 -0
  84. package/src/admin/components/fields/BlocksSetsPicker.vue +59 -0
  85. package/src/admin/components/fields/BoolField.vue +10 -0
  86. package/src/admin/components/fields/DateField.vue +22 -0
  87. package/src/admin/components/fields/EntriesField.vue +153 -0
  88. package/src/admin/components/fields/EntryField.vue +138 -0
  89. package/src/admin/components/fields/EnumField.vue +81 -0
  90. package/src/admin/components/fields/FieldRenderer.vue +87 -0
  91. package/src/admin/components/fields/GridField.vue +173 -0
  92. package/src/admin/components/fields/LinkField.vue +219 -0
  93. package/src/admin/components/fields/MediaField.vue +69 -0
  94. package/src/admin/components/fields/NumberField.vue +12 -0
  95. package/src/admin/components/fields/ObjectField.vue +18 -0
  96. package/src/admin/components/fields/RefField.vue +170 -0
  97. package/src/admin/components/fields/RepeaterField.vue +27 -0
  98. package/src/admin/components/fields/ReplicatorField.vue +121 -0
  99. package/src/admin/components/fields/TextField.vue +11 -0
  100. package/src/admin/components/fields/TextareaField.vue +11 -0
  101. package/src/admin/components/fields/VulseAccordionGroupNodeView.vue +82 -0
  102. package/src/admin/components/fields/VulseAccordionNodeView.vue +128 -0
  103. package/src/admin/components/fields/VulseCalloutNodeView.vue +81 -0
  104. package/src/admin/components/fields/VulseIframeNodeView.vue +112 -0
  105. package/src/admin/components/fields/VulseSetNodeView.vue +68 -0
  106. package/src/admin/components/fields/VulseVideoNodeView.vue +104 -0
  107. package/src/admin/components/fields/blocks-editor-extensions.ts +26 -0
  108. package/src/admin/components/fields/emoji-extension.ts +54 -0
  109. package/src/admin/components/fields/link-extension.ts +48 -0
  110. package/src/admin/components/fields/set-node-utils.ts +115 -0
  111. package/src/admin/components/fields/url-utils.ts +85 -0
  112. package/src/admin/components/fields/vulse-accordion-extension.ts +64 -0
  113. package/src/admin/components/fields/vulse-accordion-group-extension.ts +49 -0
  114. package/src/admin/components/fields/vulse-callout-extension.ts +53 -0
  115. package/src/admin/components/fields/vulse-iframe-extension.ts +96 -0
  116. package/src/admin/components/fields/vulse-set-extension.ts +66 -0
  117. package/src/admin/components/fields/vulse-video-extension.ts +65 -0
  118. package/src/admin/composables/toast.ts +35 -0
  119. package/src/admin/composables/useEntrySearch.ts +112 -0
  120. package/src/admin/composables/useSets.ts +31 -0
  121. package/src/admin/pages/collections/[name]/[id]/revisions.astro +27 -0
  122. package/src/admin/pages/collections/[name]/[id].astro +90 -0
  123. package/src/admin/pages/collections/[name]/index.astro +31 -0
  124. package/src/admin/pages/collections/[name]/new.astro +38 -0
  125. package/src/admin/pages/forms/[handle]/submissions/[id].astro +9 -0
  126. package/src/admin/pages/forms/[handle]/submissions/index.astro +9 -0
  127. package/src/admin/pages/forms/[handle].astro +9 -0
  128. package/src/admin/pages/forms/index.astro +7 -0
  129. package/src/admin/pages/forms/new.astro +7 -0
  130. package/src/admin/pages/index.astro +36 -0
  131. package/src/admin/pages/login.astro +14 -0
  132. package/src/admin/pages/media.astro +8 -0
  133. package/src/admin/pages/schema/[handle].astro +10 -0
  134. package/src/admin/pages/schema/new.astro +9 -0
  135. package/src/admin/pages/settings/auth.astro +9 -0
  136. package/src/admin/pages/settings/globals/[handle].astro +9 -0
  137. package/src/admin/pages/settings/globals/index.astro +7 -0
  138. package/src/admin/pages/settings/globals/new.astro +7 -0
  139. package/src/admin/pages/settings/index.astro +8 -0
  140. package/src/admin/pages/settings/sets/[handle].astro +9 -0
  141. package/src/admin/pages/settings/sets/index.astro +7 -0
  142. package/src/admin/pages/settings/sets/new.astro +7 -0
  143. package/src/admin/pages/users/[id].astro +10 -0
  144. package/src/admin/pages/users/index.astro +8 -0
  145. package/src/admin/styles/admin.css +166 -0
  146. package/src/core/access.ts +9 -0
  147. package/src/core/blocks/schema.ts +66 -0
  148. package/src/core/blueprints/code-to-definition.ts +156 -0
  149. package/src/core/blueprints/compile.ts +176 -0
  150. package/src/core/blueprints/define.ts +12 -0
  151. package/src/core/blueprints/definition.ts +185 -0
  152. package/src/core/blueprints/load.ts +144 -0
  153. package/src/core/blueprints/mutations.ts +236 -0
  154. package/src/core/blueprints/preview-path.ts +33 -0
  155. package/src/core/blueprints/reflect-fields.ts +305 -0
  156. package/src/core/blueprints/registry.ts +14 -0
  157. package/src/core/blueprints/seed.ts +20 -0
  158. package/src/core/blueprints/select-helpers.ts +30 -0
  159. package/src/core/blueprints/seo.ts +180 -0
  160. package/src/core/blueprints/types.ts +59 -0
  161. package/src/core/blueprints/zod-helpers.ts +86 -0
  162. package/src/core/db.ts +11 -0
  163. package/src/core/errors.ts +34 -0
  164. package/src/core/forms/compile.ts +84 -0
  165. package/src/core/forms/definition.ts +102 -0
  166. package/src/core/forms/rate-limit.ts +52 -0
  167. package/src/core/forms/unique.ts +38 -0
  168. package/src/core/globals/compile.ts +35 -0
  169. package/src/core/globals/definition.ts +27 -0
  170. package/src/core/locales.ts +45 -0
  171. package/src/core/migrations.ts +48 -0
  172. package/src/core/parse-content.ts +85 -0
  173. package/src/core/plugins/definition.ts +150 -0
  174. package/src/core/preview-content.ts +21 -0
  175. package/src/core/repos/entries.ts +504 -0
  176. package/src/core/repos/forms.ts +270 -0
  177. package/src/core/repos/globals.ts +179 -0
  178. package/src/core/repos/media.ts +106 -0
  179. package/src/core/repos/preview-sessions.ts +108 -0
  180. package/src/core/repos/revisions.ts +60 -0
  181. package/src/core/repos/settings.ts +23 -0
  182. package/src/core/schema.ts +244 -0
  183. package/src/core/sets/compile.ts +12 -0
  184. package/src/core/sets/definition.ts +10 -0
  185. package/src/core/sets/service.ts +82 -0
  186. package/src/core/sets/validate-tree.ts +57 -0
  187. package/src/core/sha256.ts +10 -0
  188. package/src/core/slug.ts +30 -0
  189. package/src/scaffold/collection-write.ts +83 -0
  190. package/src/scaffold/collection.ts +277 -0
  191. package/src/server/assets/live-preview-bridge.content.ts +2 -0
  192. package/src/server/assets/live-preview-bridge.js +535 -0
  193. package/src/server/better-auth.ts +82 -0
  194. package/src/server/cf-images.ts +34 -0
  195. package/src/server/cron.ts +37 -0
  196. package/src/server/email.ts +17 -0
  197. package/src/server/endpoints/api-auth.ts +10 -0
  198. package/src/server/endpoints/api-vulse-blueprints.ts +23 -0
  199. package/src/server/endpoints/api-vulse-entries-locales.ts +12 -0
  200. package/src/server/endpoints/api-vulse-entries-move.ts +7 -0
  201. package/src/server/endpoints/api-vulse-entries-publish.ts +7 -0
  202. package/src/server/endpoints/api-vulse-entries-tree.ts +7 -0
  203. package/src/server/endpoints/api-vulse-entries.ts +23 -0
  204. package/src/server/endpoints/api-vulse-form-handle.ts +30 -0
  205. package/src/server/endpoints/api-vulse-form-public.ts +7 -0
  206. package/src/server/endpoints/api-vulse-form-submit.ts +7 -0
  207. package/src/server/endpoints/api-vulse-form-upload.ts +7 -0
  208. package/src/server/endpoints/api-vulse-forms.ts +12 -0
  209. package/src/server/endpoints/api-vulse-globals-handle.ts +20 -0
  210. package/src/server/endpoints/api-vulse-globals-public-handle.ts +7 -0
  211. package/src/server/endpoints/api-vulse-globals-public.ts +7 -0
  212. package/src/server/endpoints/api-vulse-globals-value.ts +8 -0
  213. package/src/server/endpoints/api-vulse-globals.ts +12 -0
  214. package/src/server/endpoints/api-vulse-media-file.ts +7 -0
  215. package/src/server/endpoints/api-vulse-media-id.ts +12 -0
  216. package/src/server/endpoints/api-vulse-media.ts +12 -0
  217. package/src/server/endpoints/api-vulse-preview-bridge.ts +11 -0
  218. package/src/server/endpoints/api-vulse-preview-sessions-id.ts +10 -0
  219. package/src/server/endpoints/api-vulse-preview-sessions.ts +7 -0
  220. package/src/server/endpoints/api-vulse-preview-start.ts +7 -0
  221. package/src/server/endpoints/api-vulse-preview-stop.ts +7 -0
  222. package/src/server/endpoints/api-vulse-revisions-restore.ts +7 -0
  223. package/src/server/endpoints/api-vulse-revisions.ts +7 -0
  224. package/src/server/endpoints/api-vulse-search.ts +7 -0
  225. package/src/server/endpoints/api-vulse-sets.ts +23 -0
  226. package/src/server/endpoints/api-vulse-settings.ts +12 -0
  227. package/src/server/endpoints/api-vulse-users-id.ts +12 -0
  228. package/src/server/endpoints/api-vulse-users-reset-password.ts +7 -0
  229. package/src/server/endpoints/api-vulse-users-role.ts +9 -0
  230. package/src/server/endpoints/api-vulse-users.ts +7 -0
  231. package/src/server/endpoints/with-runtime.ts +11 -0
  232. package/src/server/env.ts +23 -0
  233. package/src/server/envelope.ts +21 -0
  234. package/src/server/forms/email.ts +11 -0
  235. package/src/server/forms/process-submission.ts +95 -0
  236. package/src/server/forms/queue.ts +25 -0
  237. package/src/server/forms/templates.ts +24 -0
  238. package/src/server/forms/webhook.ts +19 -0
  239. package/src/server/handler.ts +66 -0
  240. package/src/server/image-probe.ts +35 -0
  241. package/src/server/loader.ts +54 -0
  242. package/src/server/plugins.ts +214 -0
  243. package/src/server/preview.ts +25 -0
  244. package/src/server/r2.ts +13 -0
  245. package/src/server/routes/blueprints.ts +62 -0
  246. package/src/server/routes/entries.ts +255 -0
  247. package/src/server/routes/form-submit.ts +168 -0
  248. package/src/server/routes/form-upload.ts +100 -0
  249. package/src/server/routes/forms.ts +88 -0
  250. package/src/server/routes/globals-public.ts +30 -0
  251. package/src/server/routes/globals.ts +93 -0
  252. package/src/server/routes/media.ts +145 -0
  253. package/src/server/routes/preview-sessions.ts +76 -0
  254. package/src/server/routes/preview.ts +36 -0
  255. package/src/server/routes/revisions.ts +29 -0
  256. package/src/server/routes/search.ts +31 -0
  257. package/src/server/routes/sets.ts +40 -0
  258. package/src/server/routes/settings.ts +24 -0
  259. package/src/server/routes/users.ts +127 -0
  260. package/src/server/runtime.ts +99 -0
  261. package/src/server/sdk/collections.ts +98 -0
  262. package/src/server/sdk/index.ts +25 -0
  263. package/src/server/sdk/media.ts +11 -0
  264. package/src/server/sdk/search.ts +90 -0
@@ -0,0 +1,99 @@
1
+ import { createDb } from '../core/db.js'
2
+ import { createAuth } from './better-auth.js'
3
+ import { BlueprintRegistry } from '../core/blueprints/registry.js'
4
+ import { entriesRoutes } from './routes/entries.js'
5
+ import { revisionsRoutes } from './routes/revisions.js'
6
+ import { usersRoutes } from './routes/users.js'
7
+ import { settingsRoutes } from './routes/settings.js'
8
+ import { blueprintsRoutes } from './routes/blueprints.js'
9
+ import { setsRoutes } from './routes/sets.js'
10
+ import { mediaRoutes } from './routes/media.js'
11
+ import { searchRoutes } from './routes/search.js'
12
+ import { previewRoutes } from './routes/preview.js'
13
+ import { previewSessionsRoutes } from './routes/preview-sessions.js'
14
+ import { formsRoutes } from './routes/forms.js'
15
+ import { formSubmitRoutes } from './routes/form-submit.js'
16
+ import { formUploadRoutes } from './routes/form-upload.js'
17
+ import { globalsRoutes } from './routes/globals.js'
18
+ import { globalsPublicRoutes } from './routes/globals-public.js'
19
+ import { createSdk } from './sdk/index.js'
20
+ import { previewSecret } from './preview.js'
21
+ import type { Auth } from './better-auth.js'
22
+ import type { RuntimeEnv } from './env.js'
23
+
24
+ export interface VulseRuntime {
25
+ db: ReturnType<typeof createDb>
26
+ auth: Auth
27
+ registry: BlueprintRegistry
28
+ sdk: ReturnType<typeof createSdk>
29
+ routes: {
30
+ entries: ReturnType<typeof entriesRoutes>
31
+ revisions: ReturnType<typeof revisionsRoutes>
32
+ users: ReturnType<typeof usersRoutes>
33
+ settings: ReturnType<typeof settingsRoutes>
34
+ blueprints: ReturnType<typeof blueprintsRoutes>
35
+ sets: ReturnType<typeof setsRoutes>
36
+ media: ReturnType<typeof mediaRoutes>
37
+ search: ReturnType<typeof searchRoutes>
38
+ preview: ReturnType<typeof previewRoutes>
39
+ previewSessions: ReturnType<typeof previewSessionsRoutes>
40
+ forms: ReturnType<typeof formsRoutes>
41
+ formSubmit: ReturnType<typeof formSubmitRoutes>
42
+ formUpload: ReturnType<typeof formUploadRoutes>
43
+ globals: ReturnType<typeof globalsRoutes>
44
+ globalsPublic: ReturnType<typeof globalsPublicRoutes>
45
+ }
46
+ }
47
+
48
+ let cached: VulseRuntime | null = null
49
+
50
+ export async function getRuntime(env: RuntimeEnv, registry: BlueprintRegistry, baseURL: string): Promise<VulseRuntime> {
51
+ if (cached) return cached
52
+ const db = createDb(env.DB)
53
+ const auth = await createAuth(db, {
54
+ baseURL: env.BETTER_AUTH_URL ?? baseURL,
55
+ secret: env.BETTER_AUTH_SECRET,
56
+ ...(env.VULSE_ALLOW_MEMBER_SIGNUP === 'true' ? { allowSignUp: true } : {}),
57
+ env: env as unknown as Record<string, unknown>,
58
+ })
59
+ if (!env.BUCKET) throw new Error('Vulse: R2 binding "BUCKET" is missing. Add it to wrangler.toml.')
60
+ const cfImages = {
61
+ ...(env.CF_IMAGES_ACCOUNT_HASH ? { accountHash: env.CF_IMAGES_ACCOUNT_HASH } : {}),
62
+ ...(env.CF_IMAGES_TOKEN ? { token: env.CF_IMAGES_TOKEN } : {}),
63
+ }
64
+ cached = {
65
+ db, auth, registry,
66
+ sdk: createSdk(db, auth, registry, cfImages),
67
+ routes: {
68
+ entries: entriesRoutes(db, auth, registry),
69
+ revisions: revisionsRoutes(db, auth),
70
+ users: usersRoutes(db, auth),
71
+ settings: settingsRoutes(db, auth),
72
+ blueprints: blueprintsRoutes(db, auth),
73
+ sets: setsRoutes(db, auth),
74
+ media: mediaRoutes(db, auth, {
75
+ bucket: env.BUCKET,
76
+ cfImages,
77
+ }),
78
+ search: searchRoutes(db, auth),
79
+ preview: previewRoutes(auth, previewSecret(env)),
80
+ previewSessions: previewSessionsRoutes(db, auth, registry),
81
+ forms: formsRoutes(db, auth),
82
+ formSubmit: formSubmitRoutes(db, {
83
+ ...(env.FORM_QUEUE ? { queue: env.FORM_QUEUE } : {}),
84
+ env: env as unknown as Record<string, unknown>,
85
+ }),
86
+ formUpload: formUploadRoutes(db, { bucket: env.BUCKET }),
87
+ globals: globalsRoutes(db, auth),
88
+ globalsPublic: globalsPublicRoutes(db),
89
+ },
90
+ }
91
+ return cached
92
+ }
93
+
94
+ export function invalidateRuntime(): void { cached = null }
95
+
96
+ /** @deprecated use invalidateRuntime */
97
+ export function _resetRuntime(): void { invalidateRuntime() }
98
+
99
+ export type { RuntimeEnv } from './env.js'
@@ -0,0 +1,98 @@
1
+ import type { VulseDb } from '../../core/db.js'
2
+ import { readLocalesConfig } from '../../core/locales.js'
3
+ import { EntriesRepo, type EntryOrderBy, type EntryRow } from '../../core/repos/entries.js'
4
+ import { evaluate } from '../../core/access.js'
5
+ import type { BlueprintRegistry } from '../../core/blueprints/registry.js'
6
+ import type { AuthContext } from '../../core/blueprints/types.js'
7
+
8
+ export interface CollectionSdkOptions {
9
+ audience?: AuthContext['user'] | null
10
+ includeDrafts?: boolean
11
+ locale?: string
12
+ }
13
+
14
+ export interface CollectionFindOptions extends CollectionSdkOptions {
15
+ limit?: number
16
+ offset?: number
17
+ createdBy?: string
18
+ /** ISO date string or Date */
19
+ publishedAfter?: Date | string
20
+ publishedBefore?: Date | string
21
+ orderBy?: EntryOrderBy
22
+ order?: 'asc' | 'desc'
23
+ }
24
+
25
+ function parseDate(v: Date | string | undefined): Date | undefined {
26
+ if (v === undefined) return undefined
27
+ const d = v instanceof Date ? v : new Date(v)
28
+ return Number.isNaN(d.getTime()) ? undefined : d
29
+ }
30
+
31
+ export function collectionsSdk(db: VulseDb, reg: BlueprintRegistry) {
32
+ const entries = new EntriesRepo(db)
33
+
34
+ async function resolveLocale(explicit?: string): Promise<string> {
35
+ if (explicit) return explicit
36
+ return (await readLocalesConfig(db)).defaultLocale
37
+ }
38
+
39
+ async function gatedRows(
40
+ name: string,
41
+ audience: AuthContext['user'] | null,
42
+ listOpts: Omit<Parameters<EntriesRepo['list']>[0], 'collection' | 'status'>,
43
+ status?: 'draft' | 'published',
44
+ ): Promise<EntryRow[]> {
45
+ const bp = reg.get(name)
46
+ if (!bp) throw new Error(`Unknown collection: ${name}`)
47
+ const rows = await entries.list({ collection: name, ...(status ? { status } : {}), ...listOpts })
48
+ const out: EntryRow[] = []
49
+ for (const r of rows) {
50
+ const allowed = await evaluate(bp, 'read', {
51
+ user: audience ?? null,
52
+ entry: { id: r.id, status: r.status, createdBy: r.createdBy, content: r.content },
53
+ })
54
+ if (allowed) out.push(r)
55
+ }
56
+ return out
57
+ }
58
+
59
+ return {
60
+ find: async (collection: string, opts: CollectionFindOptions = {}) => {
61
+ const status = opts.includeDrafts ? undefined : 'published'
62
+ const publishedAfter = parseDate(opts.publishedAfter)
63
+ const publishedBefore = parseDate(opts.publishedBefore)
64
+ return gatedRows(collection, opts.audience ?? null, {
65
+ locale: await resolveLocale(opts.locale),
66
+ ...(opts.limit !== undefined ? { limit: opts.limit } : {}),
67
+ ...(opts.offset !== undefined ? { offset: opts.offset } : {}),
68
+ ...(opts.createdBy !== undefined ? { createdBy: opts.createdBy } : {}),
69
+ ...(publishedAfter !== undefined ? { publishedAfter } : {}),
70
+ ...(publishedBefore !== undefined ? { publishedBefore } : {}),
71
+ ...(opts.orderBy !== undefined ? { orderBy: opts.orderBy } : {}),
72
+ ...(opts.order !== undefined ? { order: opts.order } : {}),
73
+ }, status)
74
+ },
75
+ findById: async (collection: string, id: string, opts: CollectionSdkOptions = {}) => {
76
+ const bp = reg.get(collection)
77
+ if (!bp) throw new Error(`Unknown collection: ${collection}`)
78
+ const r = await entries.findById(id, await resolveLocale(opts.locale))
79
+ if (!r) return null
80
+ const allowed = await evaluate(bp, 'read', {
81
+ user: opts.audience ?? null,
82
+ entry: { id: r.id, status: r.status, createdBy: r.createdBy, content: r.content },
83
+ })
84
+ return allowed ? r : null
85
+ },
86
+ findBySlug: async (collection: string, slug: string, opts: CollectionSdkOptions = {}) => {
87
+ const bp = reg.get(collection)
88
+ if (!bp) throw new Error(`Unknown collection: ${collection}`)
89
+ const r = await entries.findBySlug(collection, slug, await resolveLocale(opts.locale))
90
+ if (!r) return null
91
+ const allowed = await evaluate(bp, 'read', {
92
+ user: opts.audience ?? null,
93
+ entry: { id: r.id, status: r.status, createdBy: r.createdBy, content: r.content },
94
+ })
95
+ return allowed ? r : null
96
+ },
97
+ }
98
+ }
@@ -0,0 +1,25 @@
1
+ import type { VulseDb } from '../../core/db.js'
2
+ import type { Auth } from '../better-auth.js'
3
+ import type { BlueprintRegistry } from '../../core/blueprints/registry.js'
4
+ import { collectionsSdk } from './collections.js'
5
+ import { mediaSdk } from './media.js'
6
+ import { searchSdk } from './search.js'
7
+ import type { CfImagesConfig } from '../cf-images.js'
8
+
9
+ export function createSdk(
10
+ db: VulseDb,
11
+ auth: Auth,
12
+ registry: BlueprintRegistry,
13
+ cfImages: CfImagesConfig,
14
+ ) {
15
+ return {
16
+ collections: collectionsSdk(db, registry),
17
+ media: mediaSdk(db, cfImages),
18
+ search: searchSdk(db),
19
+ auth: {
20
+ session: (request: Request) => auth.api.getSession({ headers: request.headers }),
21
+ },
22
+ }
23
+ }
24
+
25
+ export type VulseSdk = ReturnType<typeof createSdk>
@@ -0,0 +1,11 @@
1
+ import type { VulseDb } from '../../core/db.js'
2
+ import { MediaRepo } from '../../core/repos/media.js'
3
+ import { buildDeliveryUrl, type CfImagesConfig, type Variant } from '../cf-images.js'
4
+
5
+ export function mediaSdk(db: VulseDb, cfg: CfImagesConfig) {
6
+ const repo = new MediaRepo(db)
7
+ return {
8
+ url: (id: string, variant: Variant | string = 'card') => buildDeliveryUrl(cfg, id, variant),
9
+ findById: (id: string) => repo.findById(id),
10
+ }
11
+ }
@@ -0,0 +1,90 @@
1
+ import type { VulseDb } from '../../core/db.js'
2
+
3
+ export interface SearchResult {
4
+ entryId: string
5
+ collection: string
6
+ locale: string
7
+ slug: string
8
+ title: string
9
+ snippet: string
10
+ }
11
+
12
+ export interface SearchOptions {
13
+ collections?: string[]
14
+ limit?: number
15
+ includeDrafts?: boolean
16
+ locale?: string
17
+ }
18
+
19
+ function safeCollection(name: string): string {
20
+ if (!/^[a-z0-9_-]+$/.test(name)) throw new Error(`Invalid collection: ${name}`)
21
+ return name
22
+ }
23
+
24
+ function buildMatchExpression(query: string): string {
25
+ // FTS5: quote each token and append * for prefix match. Doubling " escapes it
26
+ // inside the quoted token. Tokens that reduce to empty after stripping non-
27
+ // word characters are dropped.
28
+ const tokens = query
29
+ .split(/\s+/)
30
+ .map((t) => t.replace(/[^\p{L}\p{N}_]+/gu, ''))
31
+ .filter((t) => t.length > 0)
32
+ .map((t) => `"${t.replace(/"/g, '""')}"*`)
33
+ return tokens.join(' ')
34
+ }
35
+
36
+ export function searchSdk(db: VulseDb) {
37
+ const d1 = db.$client
38
+
39
+ return {
40
+ query: async (query: string, opts: SearchOptions = {}): Promise<SearchResult[]> => {
41
+ const limit = Math.max(1, Math.min(opts.limit ?? 20, 100))
42
+ const match = buildMatchExpression(query)
43
+ if (!match) return []
44
+
45
+ const parts = [
46
+ `SELECT f.entry_id, f.collection, f.locale, f.slug, f.title,`,
47
+ `snippet(vulse_entries_fts, 4, '<mark>', '</mark>', '…', 8) AS snippet`,
48
+ `FROM vulse_entries_fts f`,
49
+ `INNER JOIN vulse_entry_locales el`,
50
+ ` ON el.entry_id = f.entry_id AND el.locale = f.locale`,
51
+ `WHERE vulse_entries_fts MATCH ?`,
52
+ ]
53
+ const binds: unknown[] = [match]
54
+
55
+ if (opts.locale) {
56
+ parts.push(`AND f.locale = ?`)
57
+ binds.push(opts.locale)
58
+ }
59
+
60
+ if (!opts.includeDrafts) parts.push(`AND el.status = 'published'`)
61
+
62
+ if (opts.collections && opts.collections.length > 0) {
63
+ const collections = opts.collections.map(safeCollection)
64
+ const placeholders = collections.map(() => '?').join(', ')
65
+ parts.push(`AND f.collection IN (${placeholders})`)
66
+ binds.push(...collections)
67
+ }
68
+
69
+ parts.push(`LIMIT ${limit}`)
70
+
71
+ const { results } = await d1.prepare(parts.join(' ')).bind(...binds).all<{
72
+ entry_id: string
73
+ collection: string
74
+ locale: string
75
+ slug: string
76
+ title: string | null
77
+ snippet: string
78
+ }>()
79
+
80
+ return results.map((r) => ({
81
+ entryId: r.entry_id,
82
+ collection: r.collection,
83
+ locale: r.locale,
84
+ slug: r.slug,
85
+ title: r.title ?? r.slug,
86
+ snippet: r.snippet,
87
+ }))
88
+ },
89
+ }
90
+ }