This commit is contained in:
2025-10-05 13:52:22 +02:00
commit 63929c2dce
65 changed files with 10729 additions and 0 deletions

8
.changeset/README.md Normal file
View File

@@ -0,0 +1,8 @@
# Changesets
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
with multi-package repos, or single-package repos to help you version and publish your code. You can
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
We have a quick list of common questions to get you started engaging with this project in
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)

11
.changeset/config.json Normal file
View File

@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "public",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": ["playground"]
}

19
.github/renovate.json vendored Normal file
View File

@@ -0,0 +1,19 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended"],
"dependencyDashboard": true,
"lockFileMaintenance": {
"enabled": true
},
"postUpdateOptions": ["pnpmDedupe"],
"packageRules": [
{
"groupName": "all dependencies",
"groupSlug": "all",
"matchPackagePatterns": ["*"],
"schedule": ["before 4am on Monday"],
"rangeStrategy": "bump"
}
],
"ignoreDeps": ["node"]
}

25
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Build
on: [pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup PNPM
run: corepack enable && pnpm -v
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 18.19.0
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm --filter astro-pocketbase build

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
node_modules
TODOS.md

26
.vscode/settings.json vendored Normal file
View File

@@ -0,0 +1,26 @@
{
"editor.defaultFormatter": "biomejs.biome",
"workbench.colorCustomizations": {
"activityBar.activeBackground": "#81bee8",
"activityBar.background": "#81bee8",
"activityBar.foreground": "#15202b",
"activityBar.inactiveForeground": "#15202b99",
"activityBarBadge.background": "#d82790",
"activityBarBadge.foreground": "#e7e7e7",
"commandCenter.border": "#15202b99",
"sash.hoverBorder": "#81bee8",
"statusBar.background": "#56a8e0",
"statusBar.foreground": "#15202b",
"statusBarItem.hoverBackground": "#2b92d8",
"statusBarItem.remoteBackground": "#56a8e0",
"statusBarItem.remoteForeground": "#15202b",
"titleBar.activeBackground": "#56a8e0",
"titleBar.activeForeground": "#15202b",
"titleBar.inactiveBackground": "#56a8e099",
"titleBar.inactiveForeground": "#15202b99"
},
"peacock.color": "#56a8e0",
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}

21
LICENSE Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 Gregory Bouteiller
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

9
README.md Normal file
View File

@@ -0,0 +1,9 @@
# astro-pocketbase
Astro integration to ease the use of PocketBase in your Astro projects
To see how to get started, check out the [docs](https://astro-pocketbase-five.vercel.app)
## Licensing
[MIT Licensed](./LICENSE). Made with ❤️ by [Gregory Bouteiller](https://github.com/gbouteiller).

21
doc/.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store

4
doc/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

11
doc/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

38
doc/CHANGELOG.md Normal file
View File

@@ -0,0 +1,38 @@
# doc
## 0.4.0
### Minor Changes
- middleware is externalized in src/lib/pocketbase/middleware.ts
## 0.3.1
### Patch Changes
- 0c3e99c: change default naming from Model to Record as pocketbase calls it
## 0.3.0
### Minor Changes
- 23c9e7f: remove eleventy fetch in favoi of pocketbase sdk and refine the way content is updated
## 0.2.0
### Minor Changes
- c1027d0: add cacheDir option for fetching
## 0.1.0
### Minor Changes
- 8901c7e: add enum options
- 99172d0: add ignore option
## 0.0.2
### Patch Changes
- 21cb879: add eleventy-fetch peer dependency mention

55
doc/README.md Normal file
View File

@@ -0,0 +1,55 @@
# Starlight Starter Kit: Basics
[![Built with Starlight](https://astro.badg.es/v2/built-with-starlight/tiny.svg)](https://starlight.astro.build)
```
npm create astro@latest -- --template starlight
```
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/starlight/tree/main/examples/basics)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/starlight/tree/main/examples/basics)
[![Deploy to Netlify](https://www.netlify.com/img/deploy/button.svg)](https://app.netlify.com/start/deploy?repository=https://github.com/withastro/starlight&create_from_path=examples/basics)
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fwithastro%2Fstarlight%2Ftree%2Fmain%2Fexamples%2Fbasics&project-name=my-starlight-docs&repository-name=my-starlight-docs)
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
## 🚀 Project Structure
Inside of your Astro + Starlight project, you'll see the following folders and files:
```
.
├── public/
├── src/
│ ├── assets/
│ ├── content/
│ │ ├── docs/
│ │ └── config.ts
│ └── env.d.ts
├── astro.config.mjs
├── package.json
└── tsconfig.json
```
Starlight looks for `.md` or `.mdx` files in the `src/content/docs/` directory. Each file is exposed as a route based on its file name.
Images can be added to `src/assets/` and embedded in Markdown with a relative link.
Static assets, like favicons, can be placed in the `public/` directory.
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Check out [Starlights docs](https://starlight.astro.build/), read [the Astro documentation](https://docs.astro.build), or jump into the [Astro Discord server](https://astro.build/chat).

38
doc/astro.config.mjs Normal file
View File

@@ -0,0 +1,38 @@
// @ts-check
import starlight from "@astrojs/starlight";
import { defineConfig } from "astro/config";
// https://astro.build/config
export default defineConfig({
integrations: [
starlight({
title: "Astro PocketBase",
social: {
github: "https://github.com/gbouteiller/astro-pocketbase",
},
sidebar: [
{
label: "Start here",
items: [
{ label: "Getting Started", slug: "start-here/getting-started" },
{ label: "Manual Setup", slug: "start-here/manual-setup" },
{ label: "Usage", slug: "start-here/usage" },
],
},
{
label: "Guides",
items: [
{ label: "Schemas and Types", slug: "guides/schemas-and-types" },
{ label: "Middleware", slug: "guides/middleware" },
{ label: "Loader", slug: "guides/loader" },
{ label: "Zod PocketBase", slug: "guides/zod-pocketbase" },
],
},
{
label: "Reference",
autogenerate: { directory: "reference" },
},
],
}),
],
});

19
doc/package.json Normal file
View File

@@ -0,0 +1,19 @@
{
"name": "doc",
"type": "module",
"version": "0.4.0",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro check && astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/check": "^0.9.4",
"@astrojs/starlight": "^0.30.3",
"astro": "^5.1.1",
"sharp": "^0.33.5",
"typescript": "^5.7.2"
}
}

1
doc/public/favicon.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128"><path fill-rule="evenodd" d="M81 36 64 0 47 36l-1 2-9-10a6 6 0 0 0-9 9l10 10h-2L0 64l36 17h2L28 91a6 6 0 1 0 9 9l9-10 1 2 17 36 17-36v-2l9 10a6 6 0 1 0 9-9l-9-9 2-1 36-17-36-17-2-1 9-9a6 6 0 1 0-9-9l-9 10v-2Zm-17 2-2 5c-4 8-11 15-19 19l-5 2 5 2c8 4 15 11 19 19l2 5 2-5c4-8 11-15 19-19l5-2-5-2c-8-4-15-11-19-19l-2-5Z" clip-rule="evenodd"/><path d="M118 19a6 6 0 0 0-9-9l-3 3a6 6 0 1 0 9 9l3-3Zm-96 4c-2 2-6 2-9 0l-3-3a6 6 0 1 1 9-9l3 3c3 2 3 6 0 9Zm0 82c-2-2-6-2-9 0l-3 3a6 6 0 1 0 9 9l3-3c3-2 3-6 0-9Zm96 4a6 6 0 0 1-9 9l-3-3a6 6 0 1 1 9-9l3 3Z"/><style>path{fill:#000}@media (prefers-color-scheme:dark){path{fill:#fff}}</style></svg>

After

Width:  |  Height:  |  Size: 696 B

BIN
doc/src/assets/houston.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View File

@@ -0,0 +1,6 @@
import { defineCollection } from 'astro:content';
import { docsSchema } from '@astrojs/starlight/schema';
export const collections = {
docs: defineCollection({ schema: docsSchema() }),
};

View File

@@ -0,0 +1,16 @@
---
title: Loader
description: Astro PocketBase gives you access to a loader for your collections
---
```ts title="src/content/config.ts"
import { pocketbaseLoader } from "src/lib/pocketbase/loader";
import { PostsRecord } from "src/lib/pocketbase/schemas";
const posts = defineCollection({
loader: pocketbaseLoader({ collection: "posts" }),
schema: PostsRecord,
});
export const collections = { posts };
```

View File

@@ -0,0 +1,12 @@
---
title: Middleware
description: Astro PocketBase gives you access to the sdk via a middleware
---
```astro title="src/page/index.astro"
---
const posts = Astro.locals.pocketbase.collection("posts").getFullList()
---
{posts.map((post) => <PostItem {post} />)}
```

View File

@@ -0,0 +1,42 @@
---
title: Schemas and Types
description: Astro PocketBase generates schemas and types for your collections
---
Astro PocketBase generates schemas and types for your collections
## Generic type
The [PocketBase SDK](https://github.com/pocketbase/js-sdk) provides a generic type for its collections called `RecordModel`:
```ts
interface BaseModel {
[key: string]: any;
id: string;
created: string;
updated: string;
}
interface RecordModel extends BaseModel {
collectionId: string;
collectionName: string;
expand?: {
[key: string]: any;
};
}
```
## Generated types
Zod schemas and types are generated for you and available for each collection in `src/lib/pocketbase/schemas`:
```ts
import { PostsRecord } from "src/lib/pocketbase/schemas";
```
The default naming is based on the [PocketBase SDK](https://github.com/pocketbase/js-sdk) convention; so for a collection named `posts`
you will be given access to a `PostsRecord` schema and a `PostsRecord` type.
:::tip
You can customize the way your collection schemas and types are named by using the dedicated [integration options](/reference/options): `nameRecordSchema` and `nameRecordType`
:::

View File

@@ -0,0 +1,92 @@
---
title: Zod PocketBase
description: Astro PocketBase guves you access to everything from Zod PocketBase
---
Astro PocketBase gives you access to everything from [Zod PocketBase](https://zod-pocketbase.vercel.app)
## Schemas Helpers
Instead of this:
```ts
import {AuthorRecord, ImageRecord, PostRecord} from "src/lib/pocketbase/schemas";
const Post = PostRecord.pick({ content: true, title: true, updated: true })
.extend({
expand: z.object({
author: AuthorRecord.pick({ name: true })
.extend({
expand: z.object({
image: ImageRecord.pick({ alt: true, src: true }),
}),
})
.transform(({ expand, ...rest }) => ({ ...rest, ...expand })),
image: ImageRecord.pick({ alt: true, src: true }),
}),
})
.transform(({ expand, ...rest }) => ({ ...rest, ...expand }));
```
Write this:
```ts
import { select } from "astro-pocketbase";
import {AuthorRecord, ImageRecord, PostRecord} from "src/lib/pocketbase/schemas";
const Post = select(PostRecord, ["content", "title", "updated"], {
author: select(AuthorRecord, ["name"], {
image: select(ImageRecord, ["alt", "src"])
}),
image: select(ImageRecord, ["alt", "src"])
});
```
:::tip[What you get]
- `expand` properties from the PocketBase SDK are automatically transformed
- `pick` properties are simplified and easier to read
Discover [expand, pick and select helpers](https://zod-pocketbase.vercel.app/guides/schemas)
:::
## Fetch Helpers
Instead of this:
```ts
import { listOptionsFrom, select } from "astro-pocketbase";
import { AuthorRecord, PostRecord } from "src/lib/pocketbase/schemas";
const Post = select(PostRecord, ["content", "title"], {
author: select(AuthorRecord, ["name"])
});
const options = listOptionsFrom(Post, { sort: "-updated" });
const { items } = await Astro.locals.pocketbase.collection("posts").getList(1, 10, options);
const firstPosts = Post.array().parse(items);
```
Write this:
```ts
import { helpersFrom, select } from "astro-pocketbase";
import { AuthorRecord, PostRecord } from "src/lib/pocketbase/schemas";
const { getRecords } = helpersFrom({ pocketbase: Astro.locals.pocketbase, cache: "1d" });
const Post = select(PostRecord, ["content", "title"], {
author: select(AuthorRecord, ["name"])
});
const { items: firstPosts } = getRecords("posts", { perPage: 10, schema: Post, sort: "-updated" });
```
:::tip[What you get]
- data is automatically validated
- options are automatically formatted without the need of other helpers (here: `listOptionsFrom`)
Discover [fetch helpers](https://zod-pocketbase.vercel.app/guides/helpers)
:::

View File

@@ -0,0 +1,30 @@
---
title: Welcome to Astro PocketBase
description: Astro integration to ease the use of PocketBase in your Astro projects
template: splash
hero:
tagline: Add PocketBase to Astro with ease!
image:
file: ../../assets/houston.webp
actions:
- text: Get started
link: /start-here/getting-started/
icon: right-arrow
- text: View on GitHub
link: https://github.com/gbouteiller/astro-pocketbase
icon: external
variant: minimal
---
import { CardGrid, Card } from '@astrojs/starlight/components';
## Also check out...
<CardGrid stagger>
<Card title="Zod PocketBase" icon="puzzle">
Add [Zod to PocketBase](https://zod-pocketbase.vercel.app) with ease!
</Card>
<Card title="Astro Superforms" icon="puzzle">
Add [Superforms to Astro](https://astro-superforms.vercel.app) with ease!
</Card>
</CardGrid>

View File

@@ -0,0 +1,62 @@
---
title: Options
description: Astro PocketBase gives you options
---
The following reference covers all supported configuration options for Astro PocketBase.
```js title="astro.config.mjs"
import { defineConfig } from "astro/config";
import pocketbase from "astro-pocketbase";
export default defineConfig({
// ...
integrations: [
pocketbase({
// Your configuration options here...
})
],
});
```
## ignore
- **Type:** `string[]`
- **Default:** `[]`
The `ignore` option allows you to ignore specific collections from being processed.
## nameEnum
- **Type:** `(enumFieldName: string) => string`
- **Default:** `(enumFieldName) => snakeCase(enumFieldName).toUpperCase()`
## nameEnumField
- **Type:** `(collectionName: string, fieldName: string) => string`
- **Default:** `(collectionName, fieldName) => collectionName + pascalName(fieldName)`
## nameEnumSchema
- **Type:** `(enumFieldName: string) => string`
- **Default:** `(enumFieldName) => pascalName(enumFieldName)`
## nameEnumType
- **Type:** `(enumFieldName: string) => string`
- **Default:** `(enumFieldName) => pascalName(enumFieldName)`
## nameEnumValues
- **Type:** `(enumFieldName: string) => string`
- **Default:** `(enumFieldName) => enumFieldName + "Values"`
## nameRecordSchema
- **Type:** `(collectionName: string) => string`
- **Default:** `(collectionName) => pascalName(collectionName) + "Record"`
## nameRecordType
- **Type:** `(collectionName: string) => string`
- **Default:** `(collectionName) => pascalName(collectionName) + "Record"`

View File

@@ -0,0 +1,51 @@
---
title: Getting started
description: How to automatically setup the Astro PocketBase integration.
---
import { Tabs, TabItem } from '@astrojs/starlight/components';
import { Steps } from '@astrojs/starlight/components';
This integration is built on top of [Zod PocketBase](https://zod-pocketbase.vercel.app) to ease the use of **PocketBase** in your **Astro** projects by:
- generating **schemas** for your selected collections
- generating a **loader** that you can use for your content collections
- generating a **middleware** that gives you access to the [PocketBase SDK](https://github.com/pocketbase/js-sdk) and helpers in `Astro.locals`
## Installation
Astro includes an `astro add` command to automate the setup of official integrations. If you prefer, you can [install integrations manually](/start-here/manual-setup) instead.
<Steps>
1. To install `astro-pocketbase`, run the following from your project directory and follow the prompts:
<Tabs>
<TabItem label="npm">
```shell
npx astro add astro-pocketbase
```
</TabItem>
<TabItem label="pnpm">
```shell
pnpm astro add astro-pocketbase
```
</TabItem>
<TabItem label="yarn">
```shell
yarn astro add astro-pocketbase
```
</TabItem>
</Tabs>
2. You also need to provide 3 environment variables for **Astro PocketBase** to retrieve your collections:
```shell
# This will only be available when run on the server!
ASTRO_POCKETBASE_ADMIN_EMAIL="admin@mydomain.com"
ASTRO_POCKETBASE_ADMIN_PASSWORD="mypassword"
# This will be available everywhere!
PUBLIC_ASTRO_POCKETBASE_URL="https://myproject.pockethost.io"
```
</Steps>
If you run into any issues, [feel free to report them to us on GitHub](https://github.com/gbouteiller/astro-pocketbase/issues) and try the manual setup instead.

View File

@@ -0,0 +1,76 @@
---
title: Manual setup
description: How to manually setup the Astro PocketBase integration.
---
import { Tabs, TabItem } from '@astrojs/starlight/components';
import { Steps } from '@astrojs/starlight/components';
## Installation
<Steps>
1. First, install the `astro-pocketbase` package:
<Tabs>
<TabItem label="npm">
```bash
npm install astro-pocketbase
```
</TabItem>
<TabItem label="pnpm">
```bash
pnpm add astro-pocketbase
```
</TabItem>
<TabItem label="yarn">
```bash
yarn add astro-pocketbase
```
</TabItem>
</Tabs>
2. Most package managers will install associated peer dependencies as well. If you see a `"Cannot find package 'pocketbase'"` (or similar)
warning when you start up Astro, you'll need to install `pocketbase`:
<Tabs>
<TabItem label="npm">
```shell
npm install pocketbase
```
</TabItem>
<TabItem label="pnpm">
```shell
pnpm add pocketbase
```
</TabItem>
<TabItem label="yarn">
```shell
yarn add pocketbase
```
</TabItem>
</Tabs>
3. Then, apply the integration to your `astro.config.*` file using the `integrations` property:
```js ins="pocketbase()" ins={2} title="astro.config.mjs"
import { defineConfig } from "astro/config";
import pocketbase from "astro-pocketbase";
export default defineConfig({
// ...
integrations: [pocketbase()],
});
```
4. You also need to provide 3 environment variables for **Astro PocketBase** to retrieve your collections:
```shell
# This will only be available when run on the server!
ASTRO_POCKETBASE_ADMIN_EMAIL="admin@mydomain.com"
ASTRO_POCKETBASE_ADMIN_PASSWORD="mypassword"
# This will be available everywhere!
PUBLIC_ASTRO_POCKETBASE_URL="https://myproject.pockethost.io"
```
</Steps>

View File

@@ -0,0 +1,32 @@
---
title: Usage
description: How to use the Astro PocketBase integration.
---
import { Tabs, TabItem } from '@astrojs/starlight/components';
## Configuration
You can configure your integration in your `astro.config.mjs`. It allows you:
- to specify the naming of the generated schemas, types and enums from your collections
- to ignore some collections from being processed
```js title="astro.config.mjs"
export default defineConfig({
// ...
integrations: [pocketbase({
// default values
ignore: [],
nameEnum: (name: string) => snakeCase(name).toUpperCase(),
nameEnumField: (collectionName: string, fieldName: string) => `${collectionName}${pascalCase(fieldName)}`,
nameEnumSchema: (name: string) => pascalCase(name),
nameEnumType: (name: string) => pascalCase(name),
nameEnumValues: (name: string) => `${name}Values`,
nameRecordSchema: (name: string) => `${pascalCase(name)}Record`,
nameRecordType: (name: string) => `${pascalCase(name)}Record`,
})],
});
```
:::tip[Options]
For more details, see the reference [here](/reference/options).
:::

2
doc/src/env.d.ts vendored Normal file
View File

@@ -0,0 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

3
doc/tsconfig.json Normal file
View File

@@ -0,0 +1,3 @@
{
"extends": "astro/tsconfigs/strict"
}

3
eslint.config.mjs Normal file
View File

@@ -0,0 +1,3 @@
import astro from "eslint-plugin-astro";
export default [...astro.configs["flat/recommended"], ...astro.configs["flat/jsx-a11y-strict"]];

28
package.json Normal file
View File

@@ -0,0 +1,28 @@
{
"name": "root",
"private": true,
"packageManager": "pnpm@9.9.0",
"engines": {
"node": ">=18.20.3"
},
"scripts": {
"doc:dev": "pnpm --filter doc dev",
"package:dev": "pnpm --filter astro-pocketbase dev",
"playground:dev": "pnpm --filter playground dev",
"dev": "pnpm --stream -r -parallel dev",
"changeset": "changeset",
"release": "node scripts/release.mjs",
"lint": "biome check .",
"lint:fix": "biome check --apply ."
},
"devDependencies": {
"@changesets/cli": "^2.27.11",
"@typescript-eslint/parser": "^8.18.1",
"eslint": "^9.17.0",
"eslint-plugin-astro": "^1.3.1",
"eslint-plugin-jsx-a11y": "^6.10.2",
"prettier": "^3.4.2",
"prettier-plugin-astro": "^0.14.1",
"prettier-plugin-tailwindcss": "^0.6.9"
}
}

1
package/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
dist

184
package/CHANGELOG.md Normal file
View File

@@ -0,0 +1,184 @@
# astro-pocketbase
## 0.11.0
### Minor Changes
- middleware is externalized in src/lib/pocketbase/middleware.ts
## 0.10.5
### Patch Changes
- update zod-pocketbase
## 0.10.4
### Patch Changes
- avoid the need of path in tsconfig.json for locals declaration
## 0.10.3
### Patch Changes
- update zod-pocketbase
## 0.10.2
### Patch Changes
- c258a34: update zod-pocketbase
## 0.10.1
### Patch Changes
- c558cce: upgrade zod-pocketbase
## 0.10.0
### Minor Changes
- add zod-pocketbase to exports
## 0.9.0
### Minor Changes
- remove magic "pocketbase:astro" and generate schemas and loader directly in "src/lib/pocketbase"
- refactor with zod-pocketbase
## 0.8.0
### Minor Changes
- 0c3e99c: add type and function helpers
### Patch Changes
- 0c3e99c: change default naming from Model to Record as pocketbase calls it
## 0.7.0
### Minor Changes
- 23c9e7f: remove eleventy fetch in favoi of pocketbase sdk and refine the way content is updated
### Patch Changes
- 23c9e7f: secure toolbar app use in astro v4
## 0.6.1
### Patch Changes
- a981658: fix toolbar app that was called during routing with ClientRouter
## 0.6.0
### Minor Changes
- c1027d0: add cacheDir option for fetching
## 0.5.6
### Patch Changes
- 5e7a9ef: Fix empty relation case
## 0.5.5
### Patch Changes
- 98693e2: fix enum type
## 0.5.4
### Patch Changes
- f72ee99: refine select field schema with enum
- f72ee99: pass collection name to stringigyFieldSchema
## 0.5.3
### Patch Changes
- 88e1af6: fix enum property name
## 0.5.2
### Patch Changes
- fc843fe: fix options schema
## 0.5.1
### Patch Changes
- 8dbbc6c: fix enum naming function used
## 0.5.0
### Minor Changes
- fba429f: add naming enum options
## 0.4.0
### Minor Changes
- cee8216: add ignore option
## 0.3.1
### Patch Changes
- 756b35e: fix loader id for refreshContent
## 0.3.0
### Minor Changes
- 257edb1: add a dev toolbar app to clear cache and refresh collections
## 0.2.3
### Patch Changes
- 120e5f5: remove useless code
- 9b462a1: add RecordRef type
## 0.2.2
### Patch Changes
- 295f9ee: add Collection type
## 0.2.1
### Patch Changes
- aeeb739: add eleventy-fetch as peer dependency
## 0.2.0
### Minor Changes
- bfe58e2: replace pocketbase sdk in the loader by eleventy fetch to ease caching
- c993dff: add cache duration option and disable cache in production
### Patch Changes
- b05e991: change astro reference to zod transform
- b05e991: correct schema type for dates
- f91a9ae: use input instead of output schema types for pocketbase sdk type
- f7c285f: replace biome by eslint and prettier
- f91a9ae: replace zod date coercion by transform to respect pocketbase sdk types
- f1e177a: correct date schema
## 0.1.0
### Minor Changes
- df2b603: project initialisation

17
package/README.md Normal file
View File

@@ -0,0 +1,17 @@
# `astro-pocketbase`
This is an [Astro integration](https://docs.astro.build/en/guides/integrations-guide/) that ease the use of PocketBase in your Astro projects
## Usage
To see how to get started, check out the [docs](https://astro-pocketbase-five.vercel.app)
## Licensing
[MIT Licensed](https://github.com/gbouteiller/astro-pocketbase/blob/main/LICENSE). Made with ❤️ by [Gregory Bouteiller](https://github.com/gbouteiller).
## Acknowledgements
- [`astro-integration-kit`](https://github.com/florian-lefebvre/astro-integration-kit) by Florian Lefebvre
- [`pocketbase`](https://github.com/pocketbase/js-sdk) by Gani Georgiev

14
package/assets/env.d.ts vendored Normal file
View File

@@ -0,0 +1,14 @@
import type { TypedPocketbase } from "../../../src/lib/pocketbase/schemas";
import type { helpersFrom } from "astro-pocketbase";
declare global {
namespace App {
interface Locals {
pocketbase: TypedPocketbase;
getRecord: ReturnType<typeof helpersFrom>["getRecord"];
getRecords: ReturnType<typeof helpersFrom>["getRecords"];
}
}
}
export {};

57
package/assets/loader.ts Normal file
View File

@@ -0,0 +1,57 @@
// This file was automatically generated by Astro PocketBase.
import type { Collection, TypedPocketbase } from "./schemas";
import type { LoaderContext } from "astro/loaders";
import Pocketbase, { type AdminAuthResponse } from "pocketbase";
let pocketbase: TypedPocketbase;
let auth: Promise<AdminAuthResponse>;
let isAuthenticating = false;
export function pocketbaseLoader({ collection }: PocketbaseLoaderOptions) {
return {
name: "pocketbase-loader",
load: async ({ store, logger, meta, parseData }: LoaderContext) => {
const { ASTRO_POCKETBASE_ADMIN_EMAIL, ASTRO_POCKETBASE_ADMIN_PASSWORD, PUBLIC_ASTRO_POCKETBASE_URL } = import.meta.env;
if (!ASTRO_POCKETBASE_ADMIN_EMAIL || !ASTRO_POCKETBASE_ADMIN_PASSWORD || !PUBLIC_ASTRO_POCKETBASE_URL)
return logger.error("Environment variables not set");
logger.info(`Loading ${collection}`);
if (!pocketbase) pocketbase = new Pocketbase(PUBLIC_ASTRO_POCKETBASE_URL);
try {
if (!isAuthenticating && !pocketbase.authStore.isValid) {
isAuthenticating = true;
auth = pocketbase.admins.authWithPassword(ASTRO_POCKETBASE_ADMIN_EMAIL, ASTRO_POCKETBASE_ADMIN_PASSWORD);
}
await auth;
const lastUpdatedItems = await pocketbase
.collection(collection)
.getList(1, 1, { fields: "updated", skipTotal: true, sort: "updated", order: "desc" });
const lastUpdated = lastUpdatedItems.items[0]?.updated;
if (lastUpdated !== meta.get("last-updated")) {
logger.info(`Refreshing ${collection}`);
meta.set("last-updated", lastUpdated);
const items = await pocketbase.collection(collection).getFullList();
for (const { id, updated, ...rest } of items) {
const data = await parseData({ id, data: { id, updated, ...rest } });
store.set({ data, digest: updated, id });
}
}
logger.info(`Loaded ${collection}`);
} catch (error) {
logger.error(`Error fetching ${collection}: ${error}`);
return;
}
},
};
}
export type PocketbaseLoaderOptions = {
collection: Collection;
};

View File

@@ -0,0 +1,17 @@
// This file was automatically generated by Astro PocketBase.
import { defineMiddleware } from "astro:middleware";
import { helpersFrom } from "astro-pocketbase";
import PocketBase from "pocketbase";
const middleware = defineMiddleware((context, next) => {
const pocketbase = new PocketBase(import.meta.env.PUBLIC_ASTRO_POCKETBASE_URL);
const { getRecord, getRecords } = helpersFrom({ pocketbase });
context.locals.pocketbase = pocketbase;
context.locals.getRecord = getRecord;
context.locals.getRecords = getRecords;
return next();
});
// You should NOT change the exported name as it is used by the Astro PocketBase integration.
export { middleware as onRequest };

17
package/assets/toolbar.ts Normal file
View File

@@ -0,0 +1,17 @@
import { defineToolbarApp } from "astro/toolbar";
export default defineToolbarApp({
init(_canvas, app, server) {
let pending = false;
// const button = document.querySelector("astro-dev-toolbar")?.shadowRoot.querySelector("button[data-app-id='astro-pocketbase']");
app.onToggled(({ state }) => {
if (!state) return;
app.toggleNotification({ level: "error", state: true });
if (pending) return;
pending = true;
server.send("astro-pocketbase:refresh", undefined);
});
},
});

1
package/env.d.ts vendored Normal file
View File

@@ -0,0 +1 @@
/// <reference types="astro/client" />

54
package/package.json Normal file
View File

@@ -0,0 +1,54 @@
{
"name": "astro-pocketbase",
"version": "0.11.0",
"description": "Astro integration to ease the use of PocketBase in your Astro projects",
"author": {
"email": "gregory.bouteiller@niama.re",
"name": "Gregory Bouteiller",
"url": "https://github.com/gbouteiller"
},
"license": "MIT",
"keywords": [
"astro-integration",
"astro-component",
"withastro",
"astro",
"pocketbase"
],
"homepage": "https://github.com/gbouteiller/astro-pocketbase",
"publishConfig": {
"access": "public"
},
"sideEffects": false,
"exports": {
".": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
}
},
"files": [
"dist",
"assets"
],
"scripts": {
"dev": "tsup --watch",
"build": "tsup"
},
"type": "module",
"peerDependencies": {
"astro": "^4.15.1",
"pocketbase": "<0.22.0",
"zod": "^3.23.8"
},
"dependencies": {
"astro-integration-kit": "^0.18.0",
"dotenv": "^16.4.7",
"es-toolkit": "^1.30.1",
"zod-pocketbase": "^0.5.0"
},
"devDependencies": {
"pocketbase": "<0.22.0",
"tsup": "^8.3.5",
"zod": "^3.24.1"
}
}

4
package/src/index.ts Normal file
View File

@@ -0,0 +1,4 @@
import { integration } from "./integration.js";
export * from "zod-pocketbase";
export default integration;

View File

@@ -0,0 +1,75 @@
import { createResolver, defineIntegration } from "astro-integration-kit";
import dotenv from "dotenv";
import { readFileSync, writeFileSync, existsSync } from "node:fs";
import type { CollectionModel } from "pocketbase";
import { Config, Credentials, defaultConfig, fetchCollections } from "zod-pocketbase";
import { generate } from "zod-pocketbase/server";
dotenv.config();
export const integration = defineIntegration({
name: "astro-pocketbase",
optionsSchema: Config.omit({ adminEmail: true, adminPassword: true, output: true, url: true }).default(defaultConfig),
setup({ options }) {
const { resolve } = createResolver(import.meta.url);
let collections: CollectionModel[] = [];
return {
hooks: {
"astro:config:setup": async (params) => {
const { addDevToolbarApp, addMiddleware, config, logger } = params;
const { srcDir } = config;
const {
ASTRO_POCKETBASE_ADMIN_EMAIL: adminEmail,
ASTRO_POCKETBASE_ADMIN_PASSWORD: adminPassword,
PUBLIC_ASTRO_POCKETBASE_URL: url,
} = process.env;
try {
const output = `${srcDir.pathname}lib/pocketbase/schemas.ts`;
const config = Config.parse({ ...options, adminEmail, adminPassword, url, output });
const credentials = Credentials.parse(config);
const allCollections = await fetchCollections(credentials);
collections = allCollections.filter(({ name }) => !config.ignore.includes(name));
await generate(collections, config);
} catch (error) {
logger.error(error instanceof Error ? error.message : "unknown error");
}
if (!existsSync(new URL("lib/pocketbase/loader.ts", srcDir))) {
const loaderContent = readFileSync(resolve("../assets/loader.ts"), "utf-8");
writeFileSync(new URL("lib/pocketbase/loader.ts", srcDir), loaderContent);
}
if (!existsSync(new URL("lib/pocketbase/middleware.ts", srcDir))) {
const middlewareContent = readFileSync(resolve("../assets/middleware.ts"), "utf-8");
writeFileSync(new URL("lib/pocketbase/middleware.ts", srcDir), middlewareContent);
}
addMiddleware({ entrypoint: new URL("lib/pocketbase/middleware.ts", srcDir), order: "pre" });
addDevToolbarApp({
id: "astro-pocketbase",
name: "Astro PocketBase",
icon: `<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>PocketBase</title><path fill="currentColor" d="M5.684 12a.632.632 0 0 1-.631-.632V4.421c0-.349.282-.632.631-.632h2.37c.46 0 .889.047 1.287.139.407.084.758.23 1.053.44.303.202.541.475.715.82.173.335.26.75.26 1.246 0 .479-.092.894-.273 1.247a2.373 2.373 0 0 1-.715.869 3.11 3.11 0 0 1-1.053.503c-.398.11-.823.164-1.273.164h-.46a.632.632 0 0 0-.632.632v1.52a.632.632 0 0 1-.632.631Zm1.279-4.888c0 .349.283.632.632.632h.343c1.04 0 1.56-.437 1.56-1.31 0-.428-.135-.73-.404-.907-.26-.176-.645-.264-1.156-.264h-.343a.632.632 0 0 0-.632.631Zm6.3 13.098a.632.632 0 0 1-.631-.631v-6.947a.63.63 0 0 1 .631-.632h2.203c.44 0 .845.034 1.216.1.38.06.708.169.984.328.276.16.492.37.647.63.164.26.246.587.246.982 0 .185-.03.37-.09.554a1.537 1.537 0 0 1-.26.516 1.857 1.857 0 0 1-1.076.7.031.031 0 0 0-.023.03c0 .015.01.028.025.03.591.111 1.04.32 1.346.626.311.31.466.743.466 1.297 0 .42-.082.78-.246 1.083a2.153 2.153 0 0 1-.685.755 3.4 3.4 0 0 1-1.036.441 5.477 5.477 0 0 1-1.268.139zm1.271-5.542c0 .349.283.631.632.631h.21c.465 0 .802-.088 1.009-.264.207-.176.31-.424.31-.743 0-.302-.107-.516-.323-.642-.207-.135-.535-.202-.984-.202h-.222a.632.632 0 0 0-.632.632Zm0 3.463c0 .349.283.631.632.631h.39c1.019 0 1.528-.369 1.528-1.108 0-.36-.125-.621-.376-.78-.241-.16-.625-.24-1.152-.24h-.39a.632.632 0 0 0-.632.632zM1.389 0C.629 0 0 .629 0 1.389V15.03a1.4 1.4 0 0 0 1.389 1.39H8.21a.632.632 0 0 0 .63-.632.632.632 0 0 0-.63-.63H1.389c-.078 0-.125-.05-.125-.128V1.39c0-.078.047-.125.125-.125H15.03c.078 0 .127.047.127.125v6.82a.632.632 0 0 0 .631.63.632.632 0 0 0 .633-.63V1.389A1.4 1.4 0 0 0 15.032 0ZM15.79 7.578a.632.632 0 0 0-.632.633.632.632 0 0 0 .631.63h6.822c.078 0 .125.05.125.128V22.61c0 .078-.047.125-.125.125H8.97c-.077 0-.127-.047-.127-.125v-6.82a.632.632 0 0 0-.631-.63.632.632 0 0 0-.633.63v6.822A1.4 1.4 0 0 0 8.968 24h13.643c.76 0 1.389-.629 1.389-1.389V8.97a1.4 1.4 0 0 0-1.389-1.39Z"/></svg>`,
entrypoint: resolve("../assets/toolbar.ts"),
});
},
"astro:config:done": ({ injectTypes }) => {
const content = readFileSync(resolve("../assets/env.d.ts"), "utf-8");
injectTypes({ filename: "env.d.ts", content });
},
//@ts-ignore
"astro:server:setup": ({ refreshContent, toolbar }) => {
toolbar.on("astro-pocketbase:refresh", async () => {
if (!refreshContent) console.warn("Content can only be refreshed in Astro v5.0.0 or higher");
await refreshContent?.({ loaders: ["pocketbase-loader"] });
});
},
},
};
},
});

9
package/tsconfig.json Normal file
View File

@@ -0,0 +1,9 @@
{
"extends": "astro/tsconfigs/strictest",
"compilerOptions": {
"module": "Node16",
"moduleResolution": "Node16",
"jsx": "preserve"
},
"exclude": ["dist"]
}

19
package/tsup.config.ts Normal file
View File

@@ -0,0 +1,19 @@
import { defineConfig } from "tsup";
import { peerDependencies } from "./package.json";
export default defineConfig((options) => {
const dev = !!options.watch;
return {
entry: ["src/**/*.(ts|js)"],
format: ["esm"],
target: "node18",
bundle: true,
dts: true,
sourcemap: true,
clean: true,
splitting: false,
minify: !dev,
external: [...Object.keys(peerDependencies)],
tsconfig: "tsconfig.json",
};
});

21
playground/.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store

4
playground/.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

11
playground/.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

54
playground/README.md Normal file
View File

@@ -0,0 +1,54 @@
# Astro Starter Kit: Basics
```sh
npm create astro@latest -- --template basics
```
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json)
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
![just-the-basics](https://github.com/withastro/astro/assets/2244813/a0a5533c-a856-4198-8470-2d67b1d7c554)
## 🚀 Project Structure
Inside of your Astro project, you'll see the following folders and files:
```text
/
├── public/
│ └── favicon.svg
├── src/
│ ├── components/
│ │ └── Card.astro
│ ├── layouts/
│ │ └── Layout.astro
│ └── pages/
│ └── index.astro
└── package.json
```
Astro looks for `.astro` or `.md` files in the `src/pages/` directory. Each page is exposed as a route based on its file name.
There's nothing special about `src/components/`, but that's where we like to put any Astro/React/Vue/Svelte/Preact components.
Any static assets, like images, can be placed in the `public/` directory.
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |
## 👀 Want to learn more?
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).

View File

@@ -0,0 +1,24 @@
import tailwind from "@astrojs/tailwind";
import { createResolver } from "astro-integration-kit";
import { hmrIntegration } from "astro-integration-kit/dev";
import { defineConfig, envField } from "astro/config";
const { default: pocketbase } = await import("astro-pocketbase");
// https://astro.build/config
export default defineConfig({
integrations: [
tailwind(),
pocketbase({ ignore: ["users"] }),
hmrIntegration({
directory: createResolver(import.meta.url).resolve("../package/dist"),
}),
],
env: {
schema: {
ASTRO_POCKETBASE_ADMIN_EMAIL: envField.string({ context: "server", access: "secret" }),
ASTRO_POCKETBASE_ADMIN_PASSWORD: envField.string({ context: "server", access: "secret" }),
PUBLIC_ASTRO_POCKETBASE_URL: envField.string({ context: "server", access: "public" }),
},
},
});

30
playground/package.json Normal file
View File

@@ -0,0 +1,30 @@
{
"name": "playground",
"type": "module",
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro check && astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@11ty/eleventy-fetch": "^5.0.1",
"@astrojs/tailwind": "^5.1.4",
"astro": "5.1.1",
"astro-integration-kit": "^0.18.0",
"astro-pocketbase": "workspace:*",
"daisyui": "^4.12.22",
"pocketbase": "<0.22.0",
"tailwindcss": "^3.4.17",
"vite": "^6.0.5",
"zod": "^3.24.1"
},
"devDependencies": {
"@astrojs/check": "^0.9.4",
"@types/node": "^22.10.2",
"typescript": "^5.7.2"
}
}

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>

After

Width:  |  Height:  |  Size: 749 B

View File

@@ -0,0 +1,25 @@
import { defineCollection } from "astro:content";
import { pocketbaseLoader } from "../lib/pocketbase/loader";
import { ConfigRecord, ImagesRecord, KnowledgesRecord, PostsRecord } from "../lib/pocketbase/schemas";
const config = defineCollection({
loader: pocketbaseLoader({ collection: "config" }),
schema: ConfigRecord,
});
const images = defineCollection({
loader: pocketbaseLoader({ collection: "images" }),
schema: ImagesRecord,
});
const knowledges = defineCollection({
loader: pocketbaseLoader({ collection: "knowledges" }),
schema: KnowledgesRecord,
});
const posts = defineCollection({
loader: pocketbaseLoader({ collection: "posts" }),
schema: PostsRecord,
});
export const collections = { config, images, knowledges, posts };

2
playground/src/env.d.ts vendored Normal file
View File

@@ -0,0 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

View File

@@ -0,0 +1,29 @@
---
import { ClientRouter } from "astro:transitions";
interface Props {
title: string;
}
const { title } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="description" content="Astro description" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
<ClientRouter />
</head>
<body>
<nav>
<a href="/">Posts</a>
<a href="/knowledges">Knowledges</a>
</nav>
<slot />
</body>
</html>

View File

@@ -0,0 +1,57 @@
// This file was automatically generated by Astro PocketBase.
import type { Collection, TypedPocketbase } from "./schemas";
import type { LoaderContext } from "astro/loaders";
import Pocketbase, { type AdminAuthResponse } from "pocketbase";
let pocketbase: TypedPocketbase;
let auth: Promise<AdminAuthResponse>;
let isAuthenticating = false;
export function pocketbaseLoader({ collection }: PocketbaseLoaderOptions) {
return {
name: "pocketbase-loader",
load: async ({ store, logger, meta, parseData }: LoaderContext) => {
const { ASTRO_POCKETBASE_ADMIN_EMAIL, ASTRO_POCKETBASE_ADMIN_PASSWORD, PUBLIC_ASTRO_POCKETBASE_URL } = import.meta.env;
if (!ASTRO_POCKETBASE_ADMIN_EMAIL || !ASTRO_POCKETBASE_ADMIN_PASSWORD || !PUBLIC_ASTRO_POCKETBASE_URL)
return logger.error("Environment variables not set");
logger.info(`Loading ${collection}`);
if (!pocketbase) pocketbase = new Pocketbase(PUBLIC_ASTRO_POCKETBASE_URL);
try {
if (!isAuthenticating && !pocketbase.authStore.isValid) {
isAuthenticating = true;
auth = pocketbase.admins.authWithPassword(ASTRO_POCKETBASE_ADMIN_EMAIL, ASTRO_POCKETBASE_ADMIN_PASSWORD);
}
await auth;
const lastUpdatedItems = await pocketbase
.collection(collection)
.getList(1, 1, { fields: "updated", skipTotal: true, sort: "updated", order: "desc" });
const lastUpdated = lastUpdatedItems.items[0]?.updated;
if (lastUpdated !== meta.get("last-updated")) {
logger.info(`Refreshing ${collection}`);
meta.set("last-updated", lastUpdated);
const items = await pocketbase.collection(collection).getFullList();
for (const { id, updated, ...rest } of items) {
const data = await parseData({ id, data: { id, updated, ...rest } });
store.set({ data, digest: updated, id });
}
}
logger.info(`Loaded ${collection}`);
} catch (error) {
logger.error(`Error fetching ${collection}: ${error}`);
return;
}
},
};
}
export type PocketbaseLoaderOptions = {
collection: Collection;
};

View File

@@ -0,0 +1,17 @@
// This file was automatically generated by Astro PocketBase.
import { defineMiddleware } from "astro:middleware";
import { helpersFrom } from "astro-pocketbase";
import PocketBase from "pocketbase";
const middleware = defineMiddleware((context, next) => {
const pocketbase = new PocketBase(import.meta.env.PUBLIC_ASTRO_POCKETBASE_URL);
const { getRecord, getRecords } = helpersFrom({ pocketbase });
context.locals.pocketbase = pocketbase;
context.locals.getRecord = getRecord;
context.locals.getRecords = getRecords;
return next();
});
// You should NOT change the exported name as it is used by the Astro PocketBase integration.
export { middleware as onRequest };

View File

@@ -0,0 +1,200 @@
import type Pocketbase from "pocketbase";
import type { RecordService } from "pocketbase";
import { z } from "zod";
/******* ENUMS *******/
export const collectionValues = [
"config",
"events",
"images",
"knowledges",
"pages",
"places",
"posts",
"products",
"services",
"testimonies",
] as const;
export const Collection = z.enum(collectionValues);
export type Collection = z.infer<typeof Collection>;
export const COLLECTION = Collection.enum;
export const servicesCategoryValues = [
"consult",
"training",
"workshop",
] as const;
export const ServicesCategory = z.enum(servicesCategoryValues);
export type ServicesCategory = z.infer<typeof ServicesCategory>;
export const SERVICES_CATEGORY = ServicesCategory.enum;
/******* BASE *******/
export const BaseModel = z.object({
created: z.string().pipe(z.coerce.date()),
id: z.string(),
updated: z.string().pipe(z.coerce.date()),
});
export type BaseModel = z.infer<typeof BaseModel>;
export const AdminModel = z.object({
...BaseModel.shape,
avatar: z.string(),
email: z.string().email(),
});
export type AdminModel = z.infer<typeof AdminModel>;
export const RecordModel = z.object({
...BaseModel.shape,
collectionId: z.string(),
collectionName: z.string(),
expand: z.any().optional(),
});
export type RecordModel = z.infer<typeof RecordModel>;
/******* RECORDS *******/
export const ConfigRecord = z.object({
...RecordModel.omit({ expand: true }).shape,
collectionName: z.literal("config"),
city: z.string(),
email: z.string().email(),
facebook: z.string().url(),
instagram: z.string().url(),
phone: z.string(),
street: z.string(),
title: z.string(),
website: z.string().url(),
zipcode: z.string(),
});
export type ConfigRecord = z.infer<typeof ConfigRecord>;
export const EventsRecord = z.object({
...RecordModel.omit({ expand: true }).shape,
collectionName: z.literal("events"),
excerpt: z.string(),
from: z.string().pipe(z.coerce.date()),
image: z.string(),
name: z.string(),
places: z.string().array(),
service: z.string(),
slug: z.string(),
to: z.string().pipe(z.coerce.date()),
url: z.string().url(),
});
export type EventsRecord = z.infer<typeof EventsRecord>;
export const ImagesRecord = z.object({
...RecordModel.omit({ expand: true }).shape,
collectionName: z.literal("images"),
alt: z.string(),
height: z.number().int(),
src: z.string(),
width: z.number().int(),
});
export type ImagesRecord = z.infer<typeof ImagesRecord>;
export const KnowledgesRecord = z.object({
...RecordModel.omit({ expand: true }).shape,
collectionName: z.literal("knowledges"),
image: z.string(),
name: z.string(),
slug: z.string(),
text: z.string(),
});
export type KnowledgesRecord = z.infer<typeof KnowledgesRecord>;
export const PagesRecord = z.object({
...RecordModel.omit({ expand: true }).shape,
collectionName: z.literal("pages"),
knowledge: z.string(),
post: z.string(),
services: z.string().array().optional(),
slug: z.string(),
testimoniesImage: z.string().transform((id) => id === "" ? undefined : id).optional(),
title: z.string(),
});
export type PagesRecord = z.infer<typeof PagesRecord>;
export const PlacesRecord = z.object({
...RecordModel.omit({ expand: true }).shape,
collectionName: z.literal("places"),
name: z.string(),
slug: z.string(),
});
export type PlacesRecord = z.infer<typeof PlacesRecord>;
export const PostsRecord = z.object({
...RecordModel.omit({ expand: true }).shape,
collectionName: z.literal("posts"),
excerpt: z.string(),
image: z.string().transform((id) => id === "" ? undefined : id).optional(),
knowledge: z.string(),
slug: z.string(),
text: z.string(),
title: z.string(),
});
export type PostsRecord = z.infer<typeof PostsRecord>;
export const ProductsRecord = z.object({
...RecordModel.omit({ expand: true }).shape,
collectionName: z.literal("products"),
excerpt: z.string(),
image: z.string(),
name: z.string(),
price: z.string(),
slug: z.string(),
text: z.string(),
url: z.string().url(),
});
export type ProductsRecord = z.infer<typeof ProductsRecord>;
export const ServicesRecord = z.object({
...RecordModel.omit({ expand: true }).shape,
collectionName: z.literal("services"),
category: ServicesCategory,
duration: z.string(),
excerpt: z.string(),
image: z.string(),
knowledge: z.string(),
name: z.string(),
places: z.string().array(),
price: z.string(),
slug: z.string(),
text: z.string(),
});
export type ServicesRecord = z.infer<typeof ServicesRecord>;
export const TestimoniesRecord = z.object({
...RecordModel.omit({ expand: true }).shape,
collectionName: z.literal("testimonies"),
author: z.string(),
text: z.string(),
title: z.string(),
});
export type TestimoniesRecord = z.infer<typeof TestimoniesRecord>;
export const records = new Map<Collection, z.AnyZodObject>([
["config", ConfigRecord],
["events", EventsRecord],
["images", ImagesRecord],
["knowledges", KnowledgesRecord],
["pages", PagesRecord],
["places", PlacesRecord],
["posts", PostsRecord],
["products", ProductsRecord],
["services", ServicesRecord],
["testimonies", TestimoniesRecord],
]);
/******* CLIENT *******/
export type TypedPocketbase = Pocketbase & {
collection(idOrName: "config"): RecordService<z.input<typeof ConfigRecord>>;
collection(idOrName: "events"): RecordService<z.input<typeof EventsRecord>>;
collection(idOrName: "images"): RecordService<z.input<typeof ImagesRecord>>;
collection(idOrName: "knowledges"): RecordService<z.input<typeof KnowledgesRecord>>;
collection(idOrName: "pages"): RecordService<z.input<typeof PagesRecord>>;
collection(idOrName: "places"): RecordService<z.input<typeof PlacesRecord>>;
collection(idOrName: "posts"): RecordService<z.input<typeof PostsRecord>>;
collection(idOrName: "products"): RecordService<z.input<typeof ProductsRecord>>;
collection(idOrName: "services"): RecordService<z.input<typeof ServicesRecord>>;
collection(idOrName: "testimonies"): RecordService<z.input<typeof TestimoniesRecord>>;
};

View File

@@ -0,0 +1,10 @@
---
import Layout from "../layouts/main.astro";
import { getCollection } from "astro:content";
const posts = await getCollection("posts");
---
<Layout title="Welcome to Astro.">
{posts.map(({ data }) => <p>{data.title}</p>)}
</Layout>

View File

@@ -0,0 +1,10 @@
---
import Layout from "../layouts/main.astro";
import { getCollection } from "astro:content";
const knowledges = await getCollection("knowledges");
---
<Layout title="Welcome to Astro.">
{knowledges.map(({ data }) => <p>{data.name}</p>)}
</Layout>

View File

@@ -0,0 +1,10 @@
import daisyui from "daisyui";
import type { Config } from "tailwindcss";
export default {
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
theme: {
extend: {},
},
plugins: [daisyui],
} satisfies Config;

7
playground/tsconfig.json Normal file
View File

@@ -0,0 +1,7 @@
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"jsx": "preserve"
},
"exclude": ["dist"]
}

8915
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

5
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,5 @@
packages:
- doc
- package
- packages/*
- playground

6
prettier.config.mjs Normal file
View File

@@ -0,0 +1,6 @@
/** @type {import("prettier").Config} */
export default {
printWidth: 140,
plugins: ["prettier-plugin-astro", "prettier-plugin-tailwindcss"],
overrides: [{ files: "*.astro", options: { parser: "astro" } }],
};

51
scripts/release.mjs Normal file
View File

@@ -0,0 +1,51 @@
import { spawn } from "node:child_process";
import { resolve } from "node:path";
/**
*
* @param {string} command
* @param {...Array<string>} args
*
* @returns {Promise<string>}
*/
const run = async (command, ...args) => {
const cwd = resolve();
return new Promise((resolve) => {
const cmd = spawn(command, args, {
stdio: ["inherit", "pipe", "pipe"], // Inherit stdin, pipe stdout, pipe stderr
shell: true,
cwd,
});
let output = "";
cmd.stdout.on("data", (data) => {
process.stdout.write(data.toString());
output += data.toString();
});
cmd.stderr.on("data", (data) => {
process.stderr.write(data.toString());
});
cmd.on("close", () => {
resolve(output);
});
});
};
const main = async () => {
await run("pnpm changeset version");
await run("git add .");
await run('git commit -m "chore: update version"');
await run("git push");
await run("pnpm --filter astro-pocketbase build");
await run("pnpm changeset publish");
await run("git push --follow-tags");
const tag = (await run("git describe --abbrev=0")).replace("\n", "");
await run(
`gh release create ${tag} --title ${tag} --notes "Please refer to [CHANGELOG.md](https://github.com/gbouteiller/astro-pocketbase/blob/main/package/CHANGELOG.md) for details."`
);
};
main();