@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.
- package/dist/cli/migrate.d.ts.map +1 -1
- package/dist/cli/migrate.js +3 -4
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +11 -10
- package/dist/core/blueprints/compile.d.ts +1 -1
- package/dist/core/blueprints/compile.d.ts.map +1 -1
- package/dist/core/blueprints/compile.js +3 -3
- package/dist/core/blueprints/mutations.js +2 -2
- package/dist/core/forms/rate-limit.d.ts +1 -1
- package/dist/core/forms/rate-limit.d.ts.map +1 -1
- package/dist/core/forms/rate-limit.js +3 -3
- package/dist/core/forms/unique.d.ts +1 -1
- package/dist/core/forms/unique.d.ts.map +1 -1
- package/dist/core/forms/unique.js +4 -4
- package/dist/core/globals/compile.d.ts +1 -1
- package/dist/core/globals/compile.d.ts.map +1 -1
- package/dist/core/globals/compile.js +2 -2
- package/dist/core/globals/definition.d.ts +1 -1
- package/dist/core/globals/definition.d.ts.map +1 -1
- package/dist/core/globals/definition.js +3 -3
- package/dist/core/repos/globals.js +3 -3
- package/dist/core/sha256.d.ts +3 -0
- package/dist/core/sha256.d.ts.map +1 -0
- package/dist/core/sha256.js +9 -0
- package/dist/integration/index.js +1 -1
- package/dist/integration/install-hook.d.ts.map +1 -1
- package/dist/integration/install-hook.js +6 -4
- package/dist/integration/wrangler-config.d.ts +12 -0
- package/dist/integration/wrangler-config.d.ts.map +1 -0
- package/dist/integration/wrangler-config.js +97 -0
- package/dist/integration/wrangler-patch.d.ts +1 -0
- package/dist/integration/wrangler-patch.d.ts.map +1 -1
- package/dist/integration/wrangler-patch.js +2 -1
- package/dist/server/routes/form-submit.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +11 -3
- package/src/admin/assets/logo-mark.svg +5 -0
- package/src/admin/client/active-locale.ts +17 -0
- package/src/admin/client/api.ts +21 -0
- package/src/admin/client/form-from-zod.ts +7 -0
- package/src/admin/client/live-preview-enabled.ts +5 -0
- package/src/admin/components/AdminShell.astro +45 -0
- package/src/admin/components/AuthSettings.vue +60 -0
- package/src/admin/components/BlockEditor.vue +53 -0
- package/src/admin/components/BlueprintEditor.vue +1783 -0
- package/src/admin/components/CollectionKindIcon.vue +26 -0
- package/src/admin/components/CollectionTree.vue +220 -0
- package/src/admin/components/EntryEditorWithPreview.vue +130 -0
- package/src/admin/components/EntryForm.vue +411 -0
- package/src/admin/components/EntryList.vue +121 -0
- package/src/admin/components/EntryStatusBadge.vue +24 -0
- package/src/admin/components/FormEditor.vue +233 -0
- package/src/admin/components/FormList.vue +54 -0
- package/src/admin/components/GlobalSetEditor.vue +272 -0
- package/src/admin/components/GlobalSetList.vue +55 -0
- package/src/admin/components/LivePreviewPanel.vue +171 -0
- package/src/admin/components/LoginForm.vue +53 -0
- package/src/admin/components/MediaLibrary.vue +106 -0
- package/src/admin/components/MediaPicker.vue +49 -0
- package/src/admin/components/RevisionDiff.vue +11 -0
- package/src/admin/components/RevisionList.vue +134 -0
- package/src/admin/components/SeoFields.vue +113 -0
- package/src/admin/components/SetEditor.vue +137 -0
- package/src/admin/components/SetList.vue +32 -0
- package/src/admin/components/SettingsForm.vue +189 -0
- package/src/admin/components/SideNav.vue +152 -0
- package/src/admin/components/SubmissionDetail.vue +45 -0
- package/src/admin/components/SubmissionList.vue +89 -0
- package/src/admin/components/ToastHost.vue +33 -0
- package/src/admin/components/TreeRow.vue +163 -0
- package/src/admin/components/UserEditor.vue +186 -0
- package/src/admin/components/UserList.vue +46 -0
- package/src/admin/components/blocks/BlockItem.vue +32 -0
- package/src/admin/components/blocks/BlockToolbar.vue +12 -0
- package/src/admin/components/blocks/edit/CodeEdit.vue +18 -0
- package/src/admin/components/blocks/edit/EmbedEdit.vue +14 -0
- package/src/admin/components/blocks/edit/HeadingEdit.vue +19 -0
- package/src/admin/components/blocks/edit/ImageEdit.vue +40 -0
- package/src/admin/components/blocks/edit/ListEdit.vue +36 -0
- package/src/admin/components/blocks/edit/ParagraphEdit.vue +14 -0
- package/src/admin/components/blocks/edit/QuoteEdit.vue +18 -0
- package/src/admin/components/fields/BlocksField.vue +123 -0
- package/src/admin/components/fields/BlocksSetsPicker.vue +59 -0
- package/src/admin/components/fields/BoolField.vue +10 -0
- package/src/admin/components/fields/DateField.vue +22 -0
- package/src/admin/components/fields/EntriesField.vue +153 -0
- package/src/admin/components/fields/EntryField.vue +138 -0
- package/src/admin/components/fields/EnumField.vue +81 -0
- package/src/admin/components/fields/FieldRenderer.vue +87 -0
- package/src/admin/components/fields/GridField.vue +173 -0
- package/src/admin/components/fields/LinkField.vue +219 -0
- package/src/admin/components/fields/MediaField.vue +69 -0
- package/src/admin/components/fields/NumberField.vue +12 -0
- package/src/admin/components/fields/ObjectField.vue +18 -0
- package/src/admin/components/fields/RefField.vue +170 -0
- package/src/admin/components/fields/RepeaterField.vue +27 -0
- package/src/admin/components/fields/ReplicatorField.vue +121 -0
- package/src/admin/components/fields/TextField.vue +11 -0
- package/src/admin/components/fields/TextareaField.vue +11 -0
- package/src/admin/components/fields/VulseAccordionGroupNodeView.vue +82 -0
- package/src/admin/components/fields/VulseAccordionNodeView.vue +128 -0
- package/src/admin/components/fields/VulseCalloutNodeView.vue +81 -0
- package/src/admin/components/fields/VulseIframeNodeView.vue +112 -0
- package/src/admin/components/fields/VulseSetNodeView.vue +68 -0
- package/src/admin/components/fields/VulseVideoNodeView.vue +104 -0
- package/src/admin/components/fields/blocks-editor-extensions.ts +26 -0
- package/src/admin/components/fields/emoji-extension.ts +54 -0
- package/src/admin/components/fields/link-extension.ts +48 -0
- package/src/admin/components/fields/set-node-utils.ts +115 -0
- package/src/admin/components/fields/url-utils.ts +85 -0
- package/src/admin/components/fields/vulse-accordion-extension.ts +64 -0
- package/src/admin/components/fields/vulse-accordion-group-extension.ts +49 -0
- package/src/admin/components/fields/vulse-callout-extension.ts +53 -0
- package/src/admin/components/fields/vulse-iframe-extension.ts +96 -0
- package/src/admin/components/fields/vulse-set-extension.ts +66 -0
- package/src/admin/components/fields/vulse-video-extension.ts +65 -0
- package/src/admin/composables/toast.ts +35 -0
- package/src/admin/composables/useEntrySearch.ts +112 -0
- package/src/admin/composables/useSets.ts +31 -0
- package/src/admin/pages/collections/[name]/[id]/revisions.astro +27 -0
- package/src/admin/pages/collections/[name]/[id].astro +90 -0
- package/src/admin/pages/collections/[name]/index.astro +31 -0
- package/src/admin/pages/collections/[name]/new.astro +38 -0
- package/src/admin/pages/forms/[handle]/submissions/[id].astro +9 -0
- package/src/admin/pages/forms/[handle]/submissions/index.astro +9 -0
- package/src/admin/pages/forms/[handle].astro +9 -0
- package/src/admin/pages/forms/index.astro +7 -0
- package/src/admin/pages/forms/new.astro +7 -0
- package/src/admin/pages/index.astro +36 -0
- package/src/admin/pages/login.astro +14 -0
- package/src/admin/pages/media.astro +8 -0
- package/src/admin/pages/schema/[handle].astro +10 -0
- package/src/admin/pages/schema/new.astro +9 -0
- package/src/admin/pages/settings/auth.astro +9 -0
- package/src/admin/pages/settings/globals/[handle].astro +9 -0
- package/src/admin/pages/settings/globals/index.astro +7 -0
- package/src/admin/pages/settings/globals/new.astro +7 -0
- package/src/admin/pages/settings/index.astro +8 -0
- package/src/admin/pages/settings/sets/[handle].astro +9 -0
- package/src/admin/pages/settings/sets/index.astro +7 -0
- package/src/admin/pages/settings/sets/new.astro +7 -0
- package/src/admin/pages/users/[id].astro +10 -0
- package/src/admin/pages/users/index.astro +8 -0
- package/src/admin/styles/admin.css +166 -0
- package/src/core/access.ts +9 -0
- package/src/core/blocks/schema.ts +66 -0
- package/src/core/blueprints/code-to-definition.ts +156 -0
- package/src/core/blueprints/compile.ts +176 -0
- package/src/core/blueprints/define.ts +12 -0
- package/src/core/blueprints/definition.ts +185 -0
- package/src/core/blueprints/load.ts +144 -0
- package/src/core/blueprints/mutations.ts +236 -0
- package/src/core/blueprints/preview-path.ts +33 -0
- package/src/core/blueprints/reflect-fields.ts +305 -0
- package/src/core/blueprints/registry.ts +14 -0
- package/src/core/blueprints/seed.ts +20 -0
- package/src/core/blueprints/select-helpers.ts +30 -0
- package/src/core/blueprints/seo.ts +180 -0
- package/src/core/blueprints/types.ts +59 -0
- package/src/core/blueprints/zod-helpers.ts +86 -0
- package/src/core/db.ts +11 -0
- package/src/core/errors.ts +34 -0
- package/src/core/forms/compile.ts +84 -0
- package/src/core/forms/definition.ts +102 -0
- package/src/core/forms/rate-limit.ts +52 -0
- package/src/core/forms/unique.ts +38 -0
- package/src/core/globals/compile.ts +35 -0
- package/src/core/globals/definition.ts +27 -0
- package/src/core/locales.ts +45 -0
- package/src/core/migrations.ts +48 -0
- package/src/core/parse-content.ts +85 -0
- package/src/core/plugins/definition.ts +150 -0
- package/src/core/preview-content.ts +21 -0
- package/src/core/repos/entries.ts +504 -0
- package/src/core/repos/forms.ts +270 -0
- package/src/core/repos/globals.ts +179 -0
- package/src/core/repos/media.ts +106 -0
- package/src/core/repos/preview-sessions.ts +108 -0
- package/src/core/repos/revisions.ts +60 -0
- package/src/core/repos/settings.ts +23 -0
- package/src/core/schema.ts +244 -0
- package/src/core/sets/compile.ts +12 -0
- package/src/core/sets/definition.ts +10 -0
- package/src/core/sets/service.ts +82 -0
- package/src/core/sets/validate-tree.ts +57 -0
- package/src/core/sha256.ts +10 -0
- package/src/core/slug.ts +30 -0
- package/src/scaffold/collection-write.ts +83 -0
- package/src/scaffold/collection.ts +277 -0
- package/src/server/assets/live-preview-bridge.content.ts +2 -0
- package/src/server/assets/live-preview-bridge.js +535 -0
- package/src/server/better-auth.ts +82 -0
- package/src/server/cf-images.ts +34 -0
- package/src/server/cron.ts +37 -0
- package/src/server/email.ts +17 -0
- package/src/server/endpoints/api-auth.ts +10 -0
- package/src/server/endpoints/api-vulse-blueprints.ts +23 -0
- package/src/server/endpoints/api-vulse-entries-locales.ts +12 -0
- package/src/server/endpoints/api-vulse-entries-move.ts +7 -0
- package/src/server/endpoints/api-vulse-entries-publish.ts +7 -0
- package/src/server/endpoints/api-vulse-entries-tree.ts +7 -0
- package/src/server/endpoints/api-vulse-entries.ts +23 -0
- package/src/server/endpoints/api-vulse-form-handle.ts +30 -0
- package/src/server/endpoints/api-vulse-form-public.ts +7 -0
- package/src/server/endpoints/api-vulse-form-submit.ts +7 -0
- package/src/server/endpoints/api-vulse-form-upload.ts +7 -0
- package/src/server/endpoints/api-vulse-forms.ts +12 -0
- package/src/server/endpoints/api-vulse-globals-handle.ts +20 -0
- package/src/server/endpoints/api-vulse-globals-public-handle.ts +7 -0
- package/src/server/endpoints/api-vulse-globals-public.ts +7 -0
- package/src/server/endpoints/api-vulse-globals-value.ts +8 -0
- package/src/server/endpoints/api-vulse-globals.ts +12 -0
- package/src/server/endpoints/api-vulse-media-file.ts +7 -0
- package/src/server/endpoints/api-vulse-media-id.ts +12 -0
- package/src/server/endpoints/api-vulse-media.ts +12 -0
- package/src/server/endpoints/api-vulse-preview-bridge.ts +11 -0
- package/src/server/endpoints/api-vulse-preview-sessions-id.ts +10 -0
- package/src/server/endpoints/api-vulse-preview-sessions.ts +7 -0
- package/src/server/endpoints/api-vulse-preview-start.ts +7 -0
- package/src/server/endpoints/api-vulse-preview-stop.ts +7 -0
- package/src/server/endpoints/api-vulse-revisions-restore.ts +7 -0
- package/src/server/endpoints/api-vulse-revisions.ts +7 -0
- package/src/server/endpoints/api-vulse-search.ts +7 -0
- package/src/server/endpoints/api-vulse-sets.ts +23 -0
- package/src/server/endpoints/api-vulse-settings.ts +12 -0
- package/src/server/endpoints/api-vulse-users-id.ts +12 -0
- package/src/server/endpoints/api-vulse-users-reset-password.ts +7 -0
- package/src/server/endpoints/api-vulse-users-role.ts +9 -0
- package/src/server/endpoints/api-vulse-users.ts +7 -0
- package/src/server/endpoints/with-runtime.ts +11 -0
- package/src/server/env.ts +23 -0
- package/src/server/envelope.ts +21 -0
- package/src/server/forms/email.ts +11 -0
- package/src/server/forms/process-submission.ts +95 -0
- package/src/server/forms/queue.ts +25 -0
- package/src/server/forms/templates.ts +24 -0
- package/src/server/forms/webhook.ts +19 -0
- package/src/server/handler.ts +66 -0
- package/src/server/image-probe.ts +35 -0
- package/src/server/loader.ts +54 -0
- package/src/server/plugins.ts +214 -0
- package/src/server/preview.ts +25 -0
- package/src/server/r2.ts +13 -0
- package/src/server/routes/blueprints.ts +62 -0
- package/src/server/routes/entries.ts +255 -0
- package/src/server/routes/form-submit.ts +168 -0
- package/src/server/routes/form-upload.ts +100 -0
- package/src/server/routes/forms.ts +88 -0
- package/src/server/routes/globals-public.ts +30 -0
- package/src/server/routes/globals.ts +93 -0
- package/src/server/routes/media.ts +145 -0
- package/src/server/routes/preview-sessions.ts +76 -0
- package/src/server/routes/preview.ts +36 -0
- package/src/server/routes/revisions.ts +29 -0
- package/src/server/routes/search.ts +31 -0
- package/src/server/routes/sets.ts +40 -0
- package/src/server/routes/settings.ts +24 -0
- package/src/server/routes/users.ts +127 -0
- package/src/server/runtime.ts +99 -0
- package/src/server/sdk/collections.ts +98 -0
- package/src/server/sdk/index.ts +25 -0
- package/src/server/sdk/media.ts +11 -0
- 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
|
+
}
|