@hollware/vue 0.0.0 → 1.0.0-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,80 @@
1
1
  # @hollware/vue
2
2
 
3
- Reserved. The Vue integration of [Hollware](https://hollware.com) will be published from https://github.com/hollware/hollware.
3
+ Read [Hollware](https://hollware.com) from a Vue 3 app that is not Nuxt (for Nuxt, use
4
+ `@hollware/nuxt`): `<HollwareContent>` renders an entry's Comark document with your own Vue
5
+ components, on the server and in the browser, and composables read published content from a
6
+ browser-only app. It imports nothing of Cloudflare.
7
+
8
+ ```sh
9
+ npm install @hollware/vue
10
+ ```
11
+
12
+ ## A browser-only app (a Vite SPA)
13
+
14
+ The composables read over HTTP with a **read token** (`hw_read_`, from `hollware token create --read`).
15
+ The token is in the page, so anyone can read it: it can only read published content, and nothing
16
+ else. Hollware accepts it from a page whose origin is in the site's `cors.origins`, so add the
17
+ origin of the app there (`http://localhost:5173` in development).
18
+
19
+ ```ts
20
+ // main.ts
21
+ import { createHollwarePlugin } from '@hollware/vue'
22
+ createApp(App)
23
+ .use(
24
+ createHollwarePlugin({ site: 'my-site', url: 'https://cms.example.com', token: 'hw_read_…' }),
25
+ )
26
+ .mount('#app')
27
+ ```
28
+
29
+ ```vue
30
+ <script setup lang="ts">
31
+ import { HollwareContent, useHollwareEntry } from '@hollware/vue'
32
+ const props = defineProps<{ slug: string }>()
33
+ const { data, error, pending } = useHollwareEntry('blog', 'en', () => props.slug)
34
+ </script>
35
+ <template>
36
+ <p v-if="pending">Loading…</p>
37
+ <p v-else-if="error">{{ error.message }}</p>
38
+ <HollwareContent v-else-if="data" :entry="data" :components="{ callout: Callout }" />
39
+ <p v-else>Not found</p>
40
+ </template>
41
+ ```
42
+
43
+ | What | Does |
44
+ | -------------------------------------------- | --------------------------------------------------------------------------------------------------- |
45
+ | `createHollwarePlugin({ site, url, token })` | The plugin that provides Hollware to the app; `provideHollware(app, options)` is the same |
46
+ | `useHollwareEntry(collection, locale, slug)` | `{ data, error, pending }` for one published entry; each argument may be a ref or a getter |
47
+ | `useHollwareEntries(collection, options?)` | `{ data, error, pending }` for a page of entries, newest first (`locale`, `tag`, `limit`, `cursor`) |
48
+
49
+ `data` is `undefined` while the first read loads and when the entry is not published (then
50
+ `pending` is `false` and `error` is `undefined`). A read that fails sets `error` and clears `data`.
51
+ When an argument changes, the composable reads again, keeps the previous `data` meanwhile, and
52
+ drops the answer of a read that a newer one replaced.
53
+
54
+ There is **no preview in the browser**: "Preview on the site" exchanges its link for a cookie that is
55
+ `HttpOnly` on the site's server, so a page in the browser never holds the token. Previews need a
56
+ server that renders the page.
57
+
58
+ ## A server that renders Vue (Vue SSR with Vite, Vike, any server)
59
+
60
+ The server helpers are the same as `@hollware/react`'s: they are `@hollware/client`'s, re-exported.
61
+
62
+ ```ts
63
+ import { createHollware, previewHeaders, previewRedirect } from '@hollware/vue'
64
+ const hollware = createHollware({ site: 'my-site', url: HOLLWARE_URL, token: HOLLWARE_TOKEN })
65
+ const redirect = previewRedirect(request) // the 303 that exchanges a preview link, or null
66
+ const answer = await hollware.getEntry('blog', 'en', slug, { request }) // { entry, preview } | undefined
67
+ const headers = previewHeaders(request) // `no-store` for a visitor who previews
68
+ ```
69
+
70
+ Render `answer.entry` with `<HollwareContent :entry="answer.entry" />` and send the same entry to the
71
+ browser to hydrate. `createHollware({ site, reader: () => hollwareBinding(env.HOLLWARE) })` reads over
72
+ a Cloudflare service binding instead. See the README of `@hollware/react` for `listEntries`,
73
+ `getTranslations` and the previews in full: they work the same.
74
+
75
+ ## Components
76
+
77
+ `<HollwareContent :entry :components>` renders the Comark document Hollware stored when the entry
78
+ was written, with `@comark/vue`. `components` maps the names of the components in the entry
79
+ (`::callout` is `callout`) to your own Vue components; an element with no mapping renders as the
80
+ plain tag. The page needs no Markdown parser: the document is already parsed.
@@ -0,0 +1,70 @@
1
+ import { EntryAnswer, EntryPage, EntryPage as EntryPage$1, GetEntryOptions, Hollware, HollwareOptions, HollwareOptions as HollwareOptions$1, HollwareReader, ListEntriesOptions, ListEntriesOptions as ListEntriesOptions$1, PREVIEW_COOKIE, PageDocument, PageEntry, PageEntry as PageEntry$1, PageSummary, ReaderOptions, Translation, createHollware, hollwareBinding, isPreviewRequest, previewCookieClear, previewHeaders, previewRedirect } from "@hollware/client";
2
+ import { App, MaybeRefOrGetter, Plugin, PropType, Ref } from "vue";
3
+ //#region src/content.d.ts
4
+ /**
5
+ * The body of an entry: the Comark document Hollware stored when it was written, rendered with
6
+ * `@comark/vue`, imported from its own subpath so no Markdown parser reaches the bundle. It
7
+ * renders on the server and hydrates in the browser from the same document. `components` maps
8
+ * the names of the components in the entry (`::callout` is `Callout`) to the site's own Vue
9
+ * components; an element with no mapping renders as the plain tag.
10
+ */
11
+ export declare const HollwareContent: import("vue").DefineComponent<import("vue").ExtractPropTypes<{
12
+ /** An entry as `getEntry` or `useHollwareEntry` gives it, with its `document`. */
13
+ entry: {
14
+ type: PropType<{
15
+ readonly document?: unknown;
16
+ } | null | undefined>;
17
+ default: null;
18
+ };
19
+ components: {
20
+ type: PropType<Record<string, unknown>>;
21
+ default: () => {};
22
+ };
23
+ }>, () => import("vue").VNode<import("vue").RendererNode, import("vue").RendererElement, {
24
+ [key: string]: any;
25
+ }> | null, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<import("vue").ExtractPropTypes<{
26
+ /** An entry as `getEntry` or `useHollwareEntry` gives it, with its `document`. */
27
+ entry: {
28
+ type: PropType<{
29
+ readonly document?: unknown;
30
+ } | null | undefined>;
31
+ default: null;
32
+ };
33
+ components: {
34
+ type: PropType<Record<string, unknown>>;
35
+ default: () => {};
36
+ };
37
+ }>> & Readonly<{}>, {
38
+ entry: {
39
+ readonly document?: unknown;
40
+ } | null | undefined;
41
+ components: Record<string, unknown>;
42
+ }, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
43
+ //#endregion
44
+ //#region src/composables.d.ts
45
+ /** Makes `app` read Hollware for the composables below. */
46
+ export declare function provideHollware(app: App, options: HollwareOptions$1): void;
47
+ /** `provideHollware` as a plugin: `createApp(App).use(createHollwarePlugin({ site, url, token }))`. */
48
+ export declare function createHollwarePlugin(options: HollwareOptions$1): Plugin;
49
+ /**
50
+ * What a composable gives: the answer, the error of the last read, and whether a read is under
51
+ * way. `data` keeps the previous answer while the next one loads, and is `undefined` after an
52
+ * error.
53
+ */
54
+ interface HollwareState<T> {
55
+ readonly data: Readonly<Ref<T | undefined>>;
56
+ readonly error: Readonly<Ref<Error | undefined>>;
57
+ readonly pending: Readonly<Ref<boolean>>;
58
+ }
59
+ /**
60
+ * A published entry with its Comark document, for `<HollwareContent>`. `data` is `undefined`
61
+ * while it loads and when the entry is not published (`pending` is `false` then, and `error` too).
62
+ */
63
+ export declare function useHollwareEntry(collection: MaybeRefOrGetter<string>, locale: MaybeRefOrGetter<string>, slug: MaybeRefOrGetter<string>): HollwareState<PageEntry$1>;
64
+ /** A page of the published entries of a collection, newest first. */
65
+ export declare function useHollwareEntries(collection: MaybeRefOrGetter<string>, options?: MaybeRefOrGetter<ListEntriesOptions$1>): HollwareState<EntryPage$1>;
66
+ //#endregion
67
+ //#region src/index.d.ts
68
+ export declare const name = "@hollware/vue";
69
+ //#endregion
70
+ export { type EntryAnswer, type EntryPage, type GetEntryOptions, type Hollware, type HollwareOptions, type HollwareReader, type HollwareState, type ListEntriesOptions, PREVIEW_COOKIE, type PageDocument, type PageEntry, type PageSummary, type ReaderOptions, type Translation, createHollware, hollwareBinding, isPreviewRequest, previewCookieClear, previewHeaders, previewRedirect };
package/dist/index.mjs ADDED
@@ -0,0 +1,110 @@
1
+ import { PREVIEW_COOKIE, createHollware, createHollware as createHollware$1, hollwareBinding, isPreviewRequest, previewCookieClear, previewHeaders, previewRedirect } from "@hollware/client";
2
+ import { MarkdownDocument } from "@comark/vue/components/MarkdownDocument";
3
+ import { defineComponent, h, inject, ref, shallowReadonly, shallowRef, toValue, watchEffect } from "vue";
4
+ //#region src/content.ts
5
+ /**
6
+ * The body of an entry: the Comark document Hollware stored when it was written, rendered with
7
+ * `@comark/vue`, imported from its own subpath so no Markdown parser reaches the bundle. It
8
+ * renders on the server and hydrates in the browser from the same document. `components` maps
9
+ * the names of the components in the entry (`::callout` is `Callout`) to the site's own Vue
10
+ * components; an element with no mapping renders as the plain tag.
11
+ */
12
+ const HollwareContent = defineComponent({
13
+ name: "HollwareContent",
14
+ props: {
15
+ /** An entry as `getEntry` or `useHollwareEntry` gives it, with its `document`. */
16
+ entry: {
17
+ type: Object,
18
+ default: null
19
+ },
20
+ components: {
21
+ type: Object,
22
+ default: () => ({})
23
+ }
24
+ },
25
+ setup(props) {
26
+ return () => {
27
+ const document = props.entry?.document;
28
+ if (typeof document !== "object" || document === null || !Array.isArray(Reflect.get(document, "nodes"))) return null;
29
+ return h(MarkdownDocument, {
30
+ value: document,
31
+ components: props.components
32
+ });
33
+ };
34
+ }
35
+ });
36
+ //#endregion
37
+ //#region src/composables.ts
38
+ /**
39
+ * Composables for a browser-only Vue app (a Vite SPA). They read published content over HTTP
40
+ * with a read token (`hw_read_`), which the browser holds: Hollware allows it from a page whose
41
+ * origin is in the site's `cors.origins`. A token in a page is public, so it must be a read
42
+ * token, never a personal one. There is no preview here: the preview cookie is `HttpOnly` on the
43
+ * site's server, so a browser never has the token to send. A site that renders on a server reads
44
+ * with `createHollware` there.
45
+ */
46
+ const HOLLWARE = Symbol("hollware");
47
+ /** Makes `app` read Hollware for the composables below. */
48
+ function provideHollware(app, options) {
49
+ app.provide(HOLLWARE, createHollware$1(options));
50
+ }
51
+ /** `provideHollware` as a plugin: `createApp(App).use(createHollwarePlugin({ site, url, token }))`. */
52
+ function createHollwarePlugin(options) {
53
+ return { install: (app) => provideHollware(app, options) };
54
+ }
55
+ function hollwareOf() {
56
+ const hollware = inject(HOLLWARE, void 0);
57
+ if (hollware === void 0) throw new Error("Hollware: nothing provides it to this app: add `app.use(createHollwarePlugin({ site, url, token }))`.");
58
+ return hollware;
59
+ }
60
+ /**
61
+ * Reads again whenever a ref that `read` reads changes, and drops the answer of a read that a
62
+ * newer one has replaced. `read` runs synchronously, so the refs it reads are the ones tracked.
63
+ */
64
+ function useRead(read) {
65
+ const hollware = hollwareOf();
66
+ const data = shallowRef();
67
+ const error = shallowRef();
68
+ const pending = ref(true);
69
+ watchEffect((onCleanup) => {
70
+ let stale = false;
71
+ onCleanup(() => {
72
+ stale = true;
73
+ });
74
+ pending.value = true;
75
+ new Promise((resolve) => resolve(read(hollware))).then((value) => {
76
+ if (stale) return;
77
+ data.value = value;
78
+ error.value = void 0;
79
+ pending.value = false;
80
+ }, (cause) => {
81
+ if (stale) return;
82
+ data.value = void 0;
83
+ error.value = cause instanceof Error ? cause : new Error(String(cause));
84
+ pending.value = false;
85
+ });
86
+ });
87
+ return {
88
+ data: shallowReadonly(data),
89
+ error: shallowReadonly(error),
90
+ pending: shallowReadonly(pending)
91
+ };
92
+ }
93
+ /**
94
+ * A published entry with its Comark document, for `<HollwareContent>`. `data` is `undefined`
95
+ * while it loads and when the entry is not published (`pending` is `false` then, and `error` too).
96
+ */
97
+ function useHollwareEntry(collection, locale, slug) {
98
+ return useRead(async (hollware) => {
99
+ return (await hollware.getEntry(toValue(collection), toValue(locale), toValue(slug)))?.entry;
100
+ });
101
+ }
102
+ /** A page of the published entries of a collection, newest first. */
103
+ function useHollwareEntries(collection, options = {}) {
104
+ return useRead((hollware) => hollware.listEntries(toValue(collection), toValue(options)));
105
+ }
106
+ //#endregion
107
+ //#region src/index.ts
108
+ const name = "@hollware/vue";
109
+ //#endregion
110
+ export { HollwareContent, PREVIEW_COOKIE, createHollware, createHollwarePlugin, hollwareBinding, isPreviewRequest, name, previewCookieClear, previewHeaders, previewRedirect, provideHollware, useHollwareEntries, useHollwareEntry };
package/package.json CHANGED
@@ -1,13 +1,53 @@
1
1
  {
2
2
  "name": "@hollware/vue",
3
- "version": "0.0.0",
4
- "description": "Reserved for Hollware's Vue integration. The first release comes from the hollware/hollware repository.",
3
+ "version": "1.0.0-rc.0",
4
+ "description": "Read Hollware from any Vue 3 app that is not Nuxt: a component that renders an entry's Comark document, server helpers, and composables for a browser-only app.",
5
5
  "homepage": "https://hollware.com",
6
+ "bugs": {
7
+ "url": "https://github.com/hollware/hollware/issues"
8
+ },
6
9
  "license": "MIT",
7
10
  "repository": {
8
11
  "type": "git",
9
12
  "url": "git+https://github.com/hollware/hollware.git",
10
13
  "directory": "packages/vue"
11
14
  },
12
- "publishConfig": { "access": "public" }
13
- }
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "type": "module",
19
+ "sideEffects": false,
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.mts",
23
+ "default": "./dist/index.mjs"
24
+ },
25
+ "./package.json": "./package.json"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public",
29
+ "provenance": true
30
+ },
31
+ "dependencies": {
32
+ "@comark/vue": "0.7.0",
33
+ "@hollware/client": "1.0.0-rc.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "24.19.2",
37
+ "publint": "0.3.25",
38
+ "typescript": "7.0.2",
39
+ "vite-plus": "1.2.0",
40
+ "vue": "3.5.44"
41
+ },
42
+ "peerDependencies": {
43
+ "vue": "^3.5.0"
44
+ },
45
+ "engines": {
46
+ "node": ">=24.11.0"
47
+ },
48
+ "scripts": {
49
+ "build": "vp pack",
50
+ "test": "vp test",
51
+ "publint": "publint --strict"
52
+ }
53
+ }