Skip to content
This repository has been archived by the owner on Nov 19, 2024. It is now read-only.

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Satoshi committed Dec 31, 2023
1 parent fccb610 commit 65c3528
Show file tree
Hide file tree
Showing 15 changed files with 6,173 additions and 0 deletions.
8 changes: 8 additions & 0 deletions .changeset/README.md
Original file line number Diff line number Diff line change
@@ -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 changes: 11 additions & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "https://unpkg.com/@changesets/[email protected]/schema.json",
"changelog": "@changesets/cli/changelog",
"commit": false,
"fixed": [],
"linked": [],
"access": "restricted",
"baseBranch": "main",
"updateInternalDependencies": "patch",
"ignore": []
}
31 changes: 31 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: Publish to NPM

on:
release:
types: [created]

concurrency: ${{ github.workflow }}-${{ github.ref }}

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: latest
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm

- run: pnpm install --frozen-lockfile

- name: Create Release Pull Request
id: changesets
uses: changesets/action@v1
with:
publish: pnpm run build
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
25 changes: 25 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
name: Run tests using Vitest

on:
push:
branches:
- '**'
pull_request:
branches:
- main

jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
with:
version: latest
- uses: actions/setup-node@v4
with:
node-version: 20
cache: pnpm

- run: pnpm install --frozen-lockfile
- run: pnpm run lint && pnpm run test
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
node_modules
dist
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
8 changes: 8 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"useTabs": true,
"singleQuote": true,
"trailingComma": "none",
"printWidth": 100,
"tabWidth": 4,
"semi": false
}
230 changes: 230 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
# PocketBase Type-safe Option Builder

Option builder for [PocketBase JavaScript SDK](https://github.com/pocketbase/js-sdk), that also helps with typing the response.

This is how you would normally write options for the PocketBase SDK:
```js
{
expand: 'comments(post),tags',
fields: 'id,title,expand.comments(post).user,expand.comments(post).message,expand.tags.id,expand.tags.name'
}
```
Writing options manually like this is very error-prone and hard to read/maintain.

This option builder allows you to write it like this instead:
```js
{
key: 'posts',
fields: ['id', 'title'],
expand: [
{ key: 'comments(post)', fields: ['user', 'message'] },
{ key: 'tags', fields: ['id', 'name'] }
]
}
```
It comes with autocomplete for `key`, `fields` and `expand` options, and also provides you a way to **type the response**.


## Installation
```sh
npm install pb-option-builder
```

## Usage

### Defining schema and relations
Below is an example of how you would define the schema for [this](https://pocketbase.io/docs/working-with-relations/) in the PocketBase docs.
```ts
interface PocketbaseCollection {
id: string
created: string
updated: string
}

interface User extends PocketBaseCollection {
name: string
}

interface Post extends PocketBaseCollection {
title: string
tags: Array<string>
}

interface Tag extends PocketBaseCollection {
name: string
}

interface Comment extends PocketBaseCollection {
post: string
user: string
message: string
}

// You need to use "type" instead of "interface" for these as interfaces are "mutable"
// TypeScript needs to know the keys are guaranteed to be of type "string"
type Schema = {
// Table names as keys
users: User
posts: Post
tags: Tag
comments: Comment
}

type Relations = {
// column names as keys
post: Post
user: User

// if the relation is one-to-many or many-to-many, use Array<>
tags: Array<Tag>

// back-relations
"posts(tags)": Array<Post>
"comments(post)": Array<Comment>
"comments(user)": Array<Comment>
}
```
### Initializing builder
```ts
import { initializeBuilder } from 'pb-option-builder'

const optionBuilder = initializeBuilder<Schema, Relations>()
```

### Building query
```ts
const [optionsObj, typeObj] = optionBuilder({
key: "posts",
// you can specify fields to be returned in the response
fields: ["id", "title", "tags"],
expand: [
{
key: "tags",
// returns all fields if it's not specified
},
{
key: "comments(post)",
// nesting "expand" is supported
expand: [
{
key: "user",
fields: ["name"]
}
]
}
]
})

const result = await pb.collection('posts').getOne(optionsObj);
```

### Typing response:

The second item in the returned array (`typeObj` in the example above) is an empty object type cast as the type of the response.
You can use it to type the response:

```ts
const result = await pb.collection('posts').getOne<typeof typeObj>(optionsObj);
```

It's a bit hacky and not very pretty, but does the job.


### Parameter type for `optionBuilder`:
```ts
{
// Table name as defined in "Schema"
key: keyof Schema

// Array of fields you want to be returned in the response
fields?: Array<keyof Schema[key]> // defaults to all fields if not specified

// Array of relations you want to be returned in the response
expand?: Array<ExpandItem>

// These will be passed to the SDK as is
sort?: string
filter?: string
requestKey?: string
}

ExpandItem {
// Relation name as defined in "Relations"
key: keyof Relations

fields?: // same as above
expand?: // same as above
}

```


### Fields

You might run into a situation where you have a component that requires a specific set of fields to be passed to it, and it makes sense to fetch the item directly in one route, but in another, it makes sense to do so through `expand`.


Because of the way the argument for the option builder is structured, the `fields` array is portable.
You can define the fields in one place, and use it either at the top level, or in the `expand` option **as is** .

Example:

```ts
// CommentBlock.svelte
export const commentFields = ["user", "message"] satisfies keyof Comment
```

```ts
// [comment]/+page.ts
import { commentFields } from '$lib/CommentBlock.svelte'

const [optionsObj, typeObj] = optionBuilder({
key: "comments",
// you can use the imported fields here
fields: commentFields
})
```

```ts
// [post]/+page.ts
import { commentFields } from '$lib/CommentBlock.svelte'

const [optionsObj, typeObj] = optionBuilder({
key: "posts",
fields: ["id", "title", "tags"],
expand: [
{
key: "comments(post)",
// or here. No need to alter the imported fields
fields: commentFields
}
]
})
```

## Caveat:
In order for back-relations to work, you need to have the forward-relations defined as well.
```ts
type Relations = {
// This alone is not enough
"comments(post)": Array<Comment>

// You need to have this defined as well
post: Post
}

const [optionsObj, typeObj] = optionBuilder({
key: "posts",
expand: [
{
// Without "post: Post", TS will complain and you won't get autocomplete or typesafety
key: "comments(post)",
}
]
})
```

## Why not just integrate this into the SDK?
- This way, you can start using this in existing projects without having to change anything. I think most of the time, you don't need to pass in any options to the SDK, so installing a new custom SDK for a very few instances where you need to seems like an overkill.
- There are many functionalities of the official SDK that I don't use or understand fully, and I don't want to maintain a fork of it just for this.
Loading

0 comments on commit 65c3528

Please sign in to comment.