@hollware/vue 0.0.0-stage → 0.11.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026-present Sergio Azócar and Hollware contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,80 @@
1
- # Temporary Holding Version
1
+ # @hollware/vue
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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,6 +1,53 @@
1
1
  {
2
2
  "name": "@hollware/vue",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.11.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
+ "homepage": "https://hollware.com",
6
+ "bugs": {
7
+ "url": "https://github.com/hollware/hollware/issues"
8
+ },
9
+ "license": "MIT",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/hollware/hollware.git",
13
+ "directory": "packages/vue"
14
+ },
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": "0.11.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
+ }
6
53
  }