mirror of
https://github.com/zhigang1992/pocketbase-typegen.git
synced 2026-01-12 09:14:16 +08:00
Support PocketBase v0.8 (#19)
* add v0.8 schema * update system fields to handle base and auth collection types * handle relation fields with multiple items * create enums for select fields that have values * additional typecheck * add user field for backwards compatability * refactor strings * refactor system types * readme * Add lint and formatting (#17) * add linting and formatting * lint codebase * prettier * update test workflow * update test workflow again * fix: url login for pocketbase 0.8.0-rc2 servers (#16) Co-authored-by: Ethan Olsen <ethan@crowdhubapps.com> * e2e integration test (#18) * add dockerfile to run e2e tests * add db typegen * cleanup * add test * add github workflow * remove interactive flag * intentionally fail integration test * save artifacts in case of failing tests * fix output dir * ignore files Co-authored-by: Ethan Olsen <46045126+o2dependent@users.noreply.github.com> Co-authored-by: Ethan Olsen <ethan@crowdhubapps.com>
This commit is contained in:
22
.eslintrc.json
Normal file
22
.eslintrc.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/recommended",
|
||||
"eslint-config-prettier"
|
||||
],
|
||||
"overrides": [],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "latest",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/ban-ts-comment": "off"
|
||||
}
|
||||
}
|
||||
32
.github/workflows/integration.yml
vendored
Normal file
32
.github/workflows/integration.yml
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
name: Integration Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main, rc]
|
||||
pull_request:
|
||||
branches: "**"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
CI: true
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run in docker
|
||||
run: |
|
||||
docker build . -t pocketbase-typegen:latest
|
||||
docker run --name integration_test pocketbase-typegen:latest
|
||||
mkdir -p output
|
||||
docker cp integration_test:/app/output output
|
||||
|
||||
- name: Archive generated type results
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: generated-types
|
||||
path: output/*
|
||||
retention-days: 5
|
||||
20
.github/workflows/test.yml
vendored
20
.github/workflows/test.yml
vendored
@@ -2,9 +2,9 @@ name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches: [main, rc]
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
branches: "**"
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -17,11 +17,11 @@ jobs:
|
||||
node-version: [16.x]
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- run: npm test
|
||||
- uses: actions/checkout@v3
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
- run: npm ci
|
||||
- run: npm run build
|
||||
- run: npm test
|
||||
|
||||
4
.prettierignore
Normal file
4
.prettierignore
Normal file
@@ -0,0 +1,4 @@
|
||||
dist
|
||||
coverage
|
||||
test/pocketbase-types-example.ts
|
||||
pocketbase-types.ts
|
||||
33
Dockerfile
Normal file
33
Dockerfile
Normal file
@@ -0,0 +1,33 @@
|
||||
# Dockerfile to run e2e integration tests against a test PocketBase server
|
||||
FROM node:16-alpine3.16
|
||||
|
||||
ARG POCKETBASE_VERSION=0.8.0-rc2
|
||||
|
||||
WORKDIR /app/output/
|
||||
WORKDIR /app/
|
||||
|
||||
# Install the dependencies
|
||||
RUN apk add --no-cache \
|
||||
ca-certificates \
|
||||
unzip \
|
||||
wget \
|
||||
zip \
|
||||
zlib-dev
|
||||
|
||||
# Download Pocketbase and install it
|
||||
ADD https://github.com/pocketbase/pocketbase/releases/download/v${POCKETBASE_VERSION}/pocketbase_${POCKETBASE_VERSION}_linux_amd64.zip /tmp/pocketbase.zip
|
||||
RUN unzip /tmp/pocketbase.zip -d /app/
|
||||
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci
|
||||
|
||||
# Copy test files
|
||||
COPY test/integration ./
|
||||
COPY test/pocketbase-types-example.ts ./
|
||||
COPY dist/index.js ./dist/index.js
|
||||
|
||||
RUN chmod +x ./pocketbase
|
||||
RUN chmod +x ./run.sh
|
||||
EXPOSE 8090
|
||||
|
||||
CMD [ "./run.sh" ]
|
||||
23
README.md
23
README.md
@@ -8,6 +8,12 @@ Generate typescript definitions from your [pocketbase.io](https://pocketbase.io/
|
||||
|
||||
This will produce types for all your PocketBase collections to use in your frontend typescript codebase.
|
||||
|
||||
## Versions
|
||||
|
||||
When using PocketBase v0.8.x, use `pocketbase-typegen` v1.1.x
|
||||
|
||||
Users of PocketBase v0.7.x should use `pocketbase-typegen` v1.0.x
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
@@ -38,11 +44,18 @@ URL example:
|
||||
|
||||
The output is a typescript file `pocketbase-types.ts` ([example](./test/pocketbase-types-example.ts)) which will contain:
|
||||
|
||||
- An enum of all collections
|
||||
- One type for each collection (eg `ProfilesRecord`)
|
||||
- One response type for each collection (eg `ProfilesResponse`) which includes base fields like id, updated, created
|
||||
- A type `CollectionRecords` mapping each collection name to the record type
|
||||
- `Collections` An enum of all collections/
|
||||
- `[CollectionName]Record` One type for each collection (eg ProfilesRecord)/
|
||||
- `[CollectionName]Response` One response type for each collection (eg ProfilesResponse) which includes system fields. This is what is returned from the PocketBase API.
|
||||
- `[CollectionName][FieldName]Options` If the collection contains a select field with set values, an enum of the options will be generated.
|
||||
- `CollectionRecords` A type mapping each collection name to the record type.
|
||||
|
||||
## Example usage
|
||||
|
||||
In the upcoming [PocketBase SDK](https://github.com/pocketbase/js-sdk) v0.8 you will be able to use generic types when fetching records, eg:
|
||||
|
||||
`pb.collection('tasks').getOne<Task>("RECORD_ID") // -> results in Promise<Task>`
|
||||
```typescript
|
||||
import { Collections, TasksResponse } from "./pocketbase-types"
|
||||
|
||||
pb.collection(Collections.Tasks).getOne<TasksResponse>("RECORD_ID") // -> results in Promise<TaskResponse>
|
||||
```
|
||||
|
||||
148
dist/index.js
vendored
148
dist/index.js
vendored
@@ -23,36 +23,46 @@ async function fromJSON(path) {
|
||||
}
|
||||
async function fromURL(url, email = "", password = "") {
|
||||
const formData = new FormData();
|
||||
formData.append("email", email);
|
||||
formData.append("identity", email);
|
||||
formData.append("password", password);
|
||||
const { token } = await fetch(`${url}/api/admins/auth-via-email`, {
|
||||
const { token } = await fetch(`${url}/api/admins/auth-with-password`, {
|
||||
method: "post",
|
||||
body: formData
|
||||
}).then((res) => res.json());
|
||||
const result = await fetch(`${url}/api/collections?perPage=200`, {
|
||||
headers: {
|
||||
Authorization: `Admin ${token}`
|
||||
Authorization: token
|
||||
}
|
||||
}).then((res) => res.json());
|
||||
return result.items;
|
||||
}
|
||||
|
||||
// src/constants.ts
|
||||
var EXPORT_COMMENT = `// This file was @generated using pocketbase-typegen`;
|
||||
var EXPORT_COMMENT = `/**
|
||||
* This file was @generated using pocketbase-typegen
|
||||
*/`;
|
||||
var RECORD_TYPE_COMMENT = `// Record types for each collection`;
|
||||
var RESPONSE_TYPE_COMMENT = `// Response types include system fields and match responses from the PocketBase API`;
|
||||
var DATE_STRING_TYPE_NAME = `IsoDateString`;
|
||||
var DATE_STRING_TYPE_DEFINITION = `export type ${DATE_STRING_TYPE_NAME} = string`;
|
||||
var RECORD_ID_STRING_NAME = `RecordIdString`;
|
||||
var RECORD_ID_STRING_DEFINITION = `export type ${RECORD_ID_STRING_NAME} = string`;
|
||||
var USER_ID_STRING_NAME = `UserIdString`;
|
||||
var USER_ID_STRING_DEFINITION = `export type ${USER_ID_STRING_NAME} = string`;
|
||||
var BASE_RECORD_DEFINITION = `export type BaseRecord = {
|
||||
var ALIAS_TYPE_DEFINITIONS = `// Alias types for improved usability
|
||||
export type ${DATE_STRING_TYPE_NAME} = string
|
||||
export type ${RECORD_ID_STRING_NAME} = string`;
|
||||
var BASE_SYSTEM_FIELDS_DEFINITION = `// System fields
|
||||
export type BaseSystemFields = {
|
||||
id: ${RECORD_ID_STRING_NAME}
|
||||
created: ${DATE_STRING_TYPE_NAME}
|
||||
updated: ${DATE_STRING_TYPE_NAME}
|
||||
"@collectionId": string
|
||||
"@collectionName": string
|
||||
"@expand"?: { [key: string]: any }
|
||||
collectionId: string
|
||||
collectionName: Collections
|
||||
expand?: { [key: string]: any }
|
||||
}`;
|
||||
var AUTH_SYSTEM_FIELDS_DEFINITION = `export type AuthSystemFields = {
|
||||
email: string
|
||||
emailVisibility: boolean
|
||||
username: string
|
||||
verified: boolean
|
||||
} & BaseSystemFields`;
|
||||
|
||||
// src/generics.ts
|
||||
function fieldNameToGeneric(name) {
|
||||
@@ -93,6 +103,12 @@ async function saveFile(outPath, typeString) {
|
||||
await fs2.writeFile(outPath, typeString, "utf8");
|
||||
console.log(`Created typescript definitions at ${outPath}`);
|
||||
}
|
||||
function getSystemFields(type) {
|
||||
return type === "auth" ? "AuthSystemFields" : "BaseSystemFields";
|
||||
}
|
||||
function getOptionEnumName(recordName, fieldName) {
|
||||
return `${toPascalCase(recordName)}${toPascalCase(fieldName)}Options`;
|
||||
}
|
||||
|
||||
// src/lib.ts
|
||||
var pbSchemaTypescriptMap = {
|
||||
@@ -102,82 +118,98 @@ var pbSchemaTypescriptMap = {
|
||||
email: "string",
|
||||
url: "string",
|
||||
date: DATE_STRING_TYPE_NAME,
|
||||
select: (fieldSchema) => fieldSchema.options.values ? fieldSchema.options.values.map((val) => `"${val}"`).join(" | ") : "string",
|
||||
select: (fieldSchema, collectionName) => fieldSchema.options.values ? getOptionEnumName(collectionName, fieldSchema.name) : "string",
|
||||
json: (fieldSchema) => `null | ${fieldNameToGeneric(fieldSchema.name)}`,
|
||||
file: (fieldSchema) => fieldSchema.options.maxSelect && fieldSchema.options.maxSelect > 1 ? "string[]" : "string",
|
||||
relation: RECORD_ID_STRING_NAME,
|
||||
user: USER_ID_STRING_NAME
|
||||
relation: (fieldSchema) => fieldSchema.options.maxSelect && fieldSchema.options.maxSelect > 1 ? `${RECORD_ID_STRING_NAME}[]` : RECORD_ID_STRING_NAME,
|
||||
user: (fieldSchema) => fieldSchema.options.maxSelect && fieldSchema.options.maxSelect > 1 ? `${RECORD_ID_STRING_NAME}[]` : RECORD_ID_STRING_NAME
|
||||
};
|
||||
function generate(results) {
|
||||
const collectionNames = [];
|
||||
const recordTypes = [];
|
||||
results.forEach((row) => {
|
||||
const responseTypes = [RESPONSE_TYPE_COMMENT];
|
||||
results.sort((a, b) => {
|
||||
if (a.name < b.name) {
|
||||
return -1;
|
||||
}
|
||||
if (a.name > b.name) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}).forEach((row) => {
|
||||
if (row.name)
|
||||
collectionNames.push(row.name);
|
||||
if (row.schema) {
|
||||
recordTypes.push(createRecordType(row.name, row.schema));
|
||||
recordTypes.push(createResponseType(row.name, row.schema));
|
||||
responseTypes.push(createResponseType(row));
|
||||
}
|
||||
});
|
||||
const sortedCollectionNames = collectionNames.sort();
|
||||
const sortedCollectionNames = collectionNames;
|
||||
const fileParts = [
|
||||
EXPORT_COMMENT,
|
||||
DATE_STRING_TYPE_DEFINITION,
|
||||
RECORD_ID_STRING_DEFINITION,
|
||||
USER_ID_STRING_DEFINITION,
|
||||
BASE_RECORD_DEFINITION,
|
||||
createCollectionEnum(sortedCollectionNames),
|
||||
...recordTypes.sort(),
|
||||
createCollectionRecord(sortedCollectionNames)
|
||||
ALIAS_TYPE_DEFINITIONS,
|
||||
BASE_SYSTEM_FIELDS_DEFINITION,
|
||||
AUTH_SYSTEM_FIELDS_DEFINITION,
|
||||
RECORD_TYPE_COMMENT,
|
||||
...recordTypes,
|
||||
responseTypes.join("\n"),
|
||||
createCollectionRecords(sortedCollectionNames)
|
||||
];
|
||||
return fileParts.join("\n\n");
|
||||
}
|
||||
function createCollectionEnum(collectionNames) {
|
||||
let typeString = `export enum Collections {
|
||||
`;
|
||||
collectionNames.forEach((name) => {
|
||||
typeString += ` ${toPascalCase(name)} = "${name}",
|
||||
`;
|
||||
});
|
||||
typeString += `}`;
|
||||
const collections = collectionNames.map((name) => ` ${toPascalCase(name)} = "${name}",`).join("\n");
|
||||
const typeString = `export enum Collections {
|
||||
${collections}
|
||||
}`;
|
||||
return typeString;
|
||||
}
|
||||
function createCollectionRecord(collectionNames) {
|
||||
let typeString = `export type CollectionRecords = {
|
||||
`;
|
||||
collectionNames.forEach((name) => {
|
||||
typeString += ` ${name}: ${toPascalCase(name)}Record
|
||||
`;
|
||||
});
|
||||
typeString += `}`;
|
||||
return typeString;
|
||||
function createCollectionRecords(collectionNames) {
|
||||
const nameRecordMap = collectionNames.map((name) => ` ${name}: ${toPascalCase(name)}Record`).join("\n");
|
||||
return `export type CollectionRecords = {
|
||||
${nameRecordMap}
|
||||
}`;
|
||||
}
|
||||
function createRecordType(name, schema) {
|
||||
let typeString = `export type ${toPascalCase(
|
||||
name
|
||||
)}Record${getGenericArgStringWithDefault(schema)} = {
|
||||
`;
|
||||
schema.forEach((fieldSchema) => {
|
||||
typeString += createTypeField(fieldSchema);
|
||||
});
|
||||
typeString += `}`;
|
||||
return typeString;
|
||||
const selectOptionEnums = createSelectOptions(name, schema);
|
||||
const typeName = toPascalCase(name);
|
||||
const genericArgs = getGenericArgStringWithDefault(schema);
|
||||
const fields = schema.map((fieldSchema) => createTypeField(name, fieldSchema)).join("\n");
|
||||
return `${selectOptionEnums}export type ${typeName}Record${genericArgs} = {
|
||||
${fields}
|
||||
}`;
|
||||
}
|
||||
function createResponseType(name, schema) {
|
||||
function createResponseType(collectionSchemaEntry) {
|
||||
const { name, schema, type } = collectionSchemaEntry;
|
||||
const pascaleName = toPascalCase(name);
|
||||
let typeString = `export type ${pascaleName}Response${getGenericArgStringWithDefault(
|
||||
schema
|
||||
)} = ${pascaleName}Record${getGenericArgString(schema)} & BaseRecord`;
|
||||
return typeString;
|
||||
const genericArgsWithDefaults = getGenericArgStringWithDefault(schema);
|
||||
const genericArgs = getGenericArgString(schema);
|
||||
const systemFields = getSystemFields(type);
|
||||
return `export type ${pascaleName}Response${genericArgsWithDefaults} = ${pascaleName}Record${genericArgs} & ${systemFields}`;
|
||||
}
|
||||
function createTypeField(fieldSchema) {
|
||||
function createTypeField(collectionName, fieldSchema) {
|
||||
if (!(fieldSchema.type in pbSchemaTypescriptMap)) {
|
||||
throw new Error(`unknown type ${fieldSchema.type} found in schema`);
|
||||
}
|
||||
const typeStringOrFunc = pbSchemaTypescriptMap[fieldSchema.type];
|
||||
const typeString = typeof typeStringOrFunc === "function" ? typeStringOrFunc(fieldSchema) : typeStringOrFunc;
|
||||
return ` ${sanitizeFieldName(fieldSchema.name)}${fieldSchema.required ? "" : "?"}: ${typeString}
|
||||
const typeString = typeof typeStringOrFunc === "function" ? typeStringOrFunc(fieldSchema, collectionName) : typeStringOrFunc;
|
||||
const fieldName = sanitizeFieldName(fieldSchema.name);
|
||||
const required = fieldSchema.required ? "" : "?";
|
||||
return ` ${fieldName}${required}: ${typeString}`;
|
||||
}
|
||||
function createSelectOptions(recordName, schema) {
|
||||
const selectFields = schema.filter((field) => field.type === "select");
|
||||
const typestring = selectFields.map(
|
||||
(field) => {
|
||||
var _a;
|
||||
return `export enum ${getOptionEnumName(recordName, field.name)} {
|
||||
${(_a = field.options.values) == null ? void 0 : _a.map((val) => ` ${val} = "${val}",`).join("\n")}
|
||||
}
|
||||
`;
|
||||
}
|
||||
).join("\n");
|
||||
return typestring;
|
||||
}
|
||||
|
||||
// src/cli.ts
|
||||
@@ -203,7 +235,7 @@ async function main(options2) {
|
||||
import { program } from "commander";
|
||||
|
||||
// package.json
|
||||
var version = "1.0.12";
|
||||
var version = "1.1.0";
|
||||
|
||||
// src/index.ts
|
||||
program.name("Pocketbase Typegen").version(version).description(
|
||||
|
||||
1985
package-lock.json
generated
1985
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pocketbase-typegen",
|
||||
"version": "1.0.13",
|
||||
"version": "1.1.0",
|
||||
"description": "Generate pocketbase record types from your database",
|
||||
"main": "dist/index.js",
|
||||
"bin": {
|
||||
@@ -23,7 +23,12 @@
|
||||
"test:update": "jest -u",
|
||||
"build": "rm -rf dist && node build.js",
|
||||
"prepublishOnly": "tsc && npm run test && npm run build",
|
||||
"typecheck": "tsc"
|
||||
"typecheck": "tsc",
|
||||
"lint": "eslint src test",
|
||||
"lint:fix": "npm run lint -- --fix",
|
||||
"prettier": "prettier src test --check",
|
||||
"prettier:fix": "npm run prettier -- --write",
|
||||
"format": "npm run prettier:fix && npm run lint:fix"
|
||||
},
|
||||
"author": "@patmood",
|
||||
"license": "ISC",
|
||||
@@ -37,14 +42,30 @@
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.1.2",
|
||||
"@types/node": "^18.8.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.42.1",
|
||||
"@typescript-eslint/parser": "^5.42.1",
|
||||
"esbuild": "^0.15.11",
|
||||
"esbuild-node-externals": "^1.5.0",
|
||||
"eslint": "^8.27.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"jest": "^29.1.2",
|
||||
"prettier": "^2.7.1",
|
||||
"ts-jest": "^29.0.3",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"typescript": "^4.8.4"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "ts-jest",
|
||||
"testEnvironment": "node"
|
||||
"testEnvironment": "node",
|
||||
"modulePathIgnorePatterns": [
|
||||
"<rootDir>/dist/",
|
||||
"<rootDir>/test/pocketbase-types-examples.ts"
|
||||
]
|
||||
},
|
||||
"prettier": {
|
||||
"trailingComma": "es5",
|
||||
"tabWidth": 2,
|
||||
"semi": false,
|
||||
"singleQuote": false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,27 @@
|
||||
export const EXPORT_COMMENT = `// This file was @generated using pocketbase-typegen`
|
||||
export const EXPORT_COMMENT = `/**
|
||||
* This file was @generated using pocketbase-typegen
|
||||
*/`
|
||||
export const RECORD_TYPE_COMMENT = `// Record types for each collection`
|
||||
export const RESPONSE_TYPE_COMMENT = `// Response types include system fields and match responses from the PocketBase API`
|
||||
export const DATE_STRING_TYPE_NAME = `IsoDateString`
|
||||
export const DATE_STRING_TYPE_DEFINITION = `export type ${DATE_STRING_TYPE_NAME} = string`
|
||||
export const RECORD_ID_STRING_NAME = `RecordIdString`
|
||||
export const RECORD_ID_STRING_DEFINITION = `export type ${RECORD_ID_STRING_NAME} = string`
|
||||
export const USER_ID_STRING_NAME = `UserIdString`
|
||||
export const USER_ID_STRING_DEFINITION = `export type ${USER_ID_STRING_NAME} = string`
|
||||
export const BASE_RECORD_DEFINITION = `export type BaseRecord = {
|
||||
export const ALIAS_TYPE_DEFINITIONS = `// Alias types for improved usability
|
||||
export type ${DATE_STRING_TYPE_NAME} = string
|
||||
export type ${RECORD_ID_STRING_NAME} = string`
|
||||
|
||||
export const BASE_SYSTEM_FIELDS_DEFINITION = `// System fields
|
||||
export type BaseSystemFields = {
|
||||
\tid: ${RECORD_ID_STRING_NAME}
|
||||
\tcreated: ${DATE_STRING_TYPE_NAME}
|
||||
\tupdated: ${DATE_STRING_TYPE_NAME}
|
||||
\t"@collectionId": string
|
||||
\t"@collectionName": string
|
||||
\t"@expand"?: { [key: string]: any }
|
||||
\tcollectionId: string
|
||||
\tcollectionName: Collections
|
||||
\texpand?: { [key: string]: any }
|
||||
}`
|
||||
|
||||
export const AUTH_SYSTEM_FIELDS_DEFINITION = `export type AuthSystemFields = {
|
||||
\temail: string
|
||||
\temailVisibility: boolean
|
||||
\tusername: string
|
||||
\tverified: boolean
|
||||
} & BaseSystemFields`
|
||||
|
||||
157
src/lib.ts
157
src/lib.ts
@@ -1,12 +1,12 @@
|
||||
import {
|
||||
BASE_RECORD_DEFINITION,
|
||||
DATE_STRING_TYPE_DEFINITION,
|
||||
ALIAS_TYPE_DEFINITIONS,
|
||||
AUTH_SYSTEM_FIELDS_DEFINITION,
|
||||
BASE_SYSTEM_FIELDS_DEFINITION,
|
||||
DATE_STRING_TYPE_NAME,
|
||||
EXPORT_COMMENT,
|
||||
RECORD_ID_STRING_DEFINITION,
|
||||
RECORD_ID_STRING_NAME,
|
||||
USER_ID_STRING_DEFINITION,
|
||||
USER_ID_STRING_NAME,
|
||||
RECORD_TYPE_COMMENT,
|
||||
RESPONSE_TYPE_COMMENT,
|
||||
} from "./constants"
|
||||
import { CollectionRecord, FieldSchema } from "./types"
|
||||
import {
|
||||
@@ -14,7 +14,12 @@ import {
|
||||
getGenericArgString,
|
||||
getGenericArgStringWithDefault,
|
||||
} from "./generics"
|
||||
import { sanitizeFieldName, toPascalCase } from "./utils"
|
||||
import {
|
||||
getOptionEnumName,
|
||||
getSystemFields,
|
||||
sanitizeFieldName,
|
||||
toPascalCase,
|
||||
} from "./utils"
|
||||
|
||||
const pbSchemaTypescriptMap = {
|
||||
text: "string",
|
||||
@@ -23,9 +28,9 @@ const pbSchemaTypescriptMap = {
|
||||
email: "string",
|
||||
url: "string",
|
||||
date: DATE_STRING_TYPE_NAME,
|
||||
select: (fieldSchema: FieldSchema) =>
|
||||
select: (fieldSchema: FieldSchema, collectionName: string) =>
|
||||
fieldSchema.options.values
|
||||
? fieldSchema.options.values.map((val) => `"${val}"`).join(" | ")
|
||||
? getOptionEnumName(collectionName, fieldSchema.name)
|
||||
: "string",
|
||||
json: (fieldSchema: FieldSchema) =>
|
||||
`null | ${fieldNameToGeneric(fieldSchema.name)}`,
|
||||
@@ -33,78 +38,105 @@ const pbSchemaTypescriptMap = {
|
||||
fieldSchema.options.maxSelect && fieldSchema.options.maxSelect > 1
|
||||
? "string[]"
|
||||
: "string",
|
||||
relation: RECORD_ID_STRING_NAME,
|
||||
user: USER_ID_STRING_NAME,
|
||||
relation: (fieldSchema: FieldSchema) =>
|
||||
fieldSchema.options.maxSelect && fieldSchema.options.maxSelect > 1
|
||||
? `${RECORD_ID_STRING_NAME}[]`
|
||||
: RECORD_ID_STRING_NAME,
|
||||
// DEPRECATED: PocketBase v0.8 does not have a dedicated user relation
|
||||
user: (fieldSchema: FieldSchema) =>
|
||||
fieldSchema.options.maxSelect && fieldSchema.options.maxSelect > 1
|
||||
? `${RECORD_ID_STRING_NAME}[]`
|
||||
: RECORD_ID_STRING_NAME,
|
||||
}
|
||||
|
||||
export function generate(results: Array<CollectionRecord>) {
|
||||
const collectionNames: Array<string> = []
|
||||
const recordTypes: Array<string> = []
|
||||
const responseTypes: Array<string> = [RESPONSE_TYPE_COMMENT]
|
||||
|
||||
results.forEach((row) => {
|
||||
if (row.name) collectionNames.push(row.name)
|
||||
if (row.schema) {
|
||||
recordTypes.push(createRecordType(row.name, row.schema))
|
||||
recordTypes.push(createResponseType(row.name, row.schema))
|
||||
}
|
||||
})
|
||||
const sortedCollectionNames = collectionNames.sort()
|
||||
results
|
||||
.sort((a, b) => {
|
||||
if (a.name < b.name) {
|
||||
return -1
|
||||
}
|
||||
if (a.name > b.name) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
.forEach((row) => {
|
||||
if (row.name) collectionNames.push(row.name)
|
||||
if (row.schema) {
|
||||
recordTypes.push(createRecordType(row.name, row.schema))
|
||||
responseTypes.push(createResponseType(row))
|
||||
}
|
||||
})
|
||||
const sortedCollectionNames = collectionNames
|
||||
|
||||
const fileParts = [
|
||||
EXPORT_COMMENT,
|
||||
DATE_STRING_TYPE_DEFINITION,
|
||||
RECORD_ID_STRING_DEFINITION,
|
||||
USER_ID_STRING_DEFINITION,
|
||||
BASE_RECORD_DEFINITION,
|
||||
createCollectionEnum(sortedCollectionNames),
|
||||
...recordTypes.sort(),
|
||||
createCollectionRecord(sortedCollectionNames),
|
||||
ALIAS_TYPE_DEFINITIONS,
|
||||
BASE_SYSTEM_FIELDS_DEFINITION,
|
||||
AUTH_SYSTEM_FIELDS_DEFINITION,
|
||||
RECORD_TYPE_COMMENT,
|
||||
...recordTypes,
|
||||
responseTypes.join("\n"),
|
||||
createCollectionRecords(sortedCollectionNames),
|
||||
]
|
||||
|
||||
return fileParts.join("\n\n")
|
||||
}
|
||||
|
||||
export function createCollectionEnum(collectionNames: Array<string>) {
|
||||
let typeString = `export enum Collections {\n`
|
||||
collectionNames.forEach((name) => {
|
||||
typeString += `\t${toPascalCase(name)} = "${name}",\n`
|
||||
})
|
||||
typeString += `}`
|
||||
const collections = collectionNames
|
||||
.map((name) => `\t${toPascalCase(name)} = "${name}",`)
|
||||
.join("\n")
|
||||
const typeString = `export enum Collections {
|
||||
${collections}
|
||||
}`
|
||||
return typeString
|
||||
}
|
||||
|
||||
export function createCollectionRecord(collectionNames: Array<string>) {
|
||||
let typeString = `export type CollectionRecords = {\n`
|
||||
collectionNames.forEach((name) => {
|
||||
typeString += `\t${name}: ${toPascalCase(name)}Record\n`
|
||||
})
|
||||
typeString += `}`
|
||||
return typeString
|
||||
export function createCollectionRecords(collectionNames: Array<string>) {
|
||||
const nameRecordMap = collectionNames
|
||||
.map((name) => `\t${name}: ${toPascalCase(name)}Record`)
|
||||
.join("\n")
|
||||
return `export type CollectionRecords = {
|
||||
${nameRecordMap}
|
||||
}`
|
||||
}
|
||||
|
||||
export function createRecordType(
|
||||
name: string,
|
||||
schema: Array<FieldSchema>
|
||||
): string {
|
||||
let typeString = `export type ${toPascalCase(
|
||||
name
|
||||
)}Record${getGenericArgStringWithDefault(schema)} = {\n`
|
||||
schema.forEach((fieldSchema: FieldSchema) => {
|
||||
typeString += createTypeField(fieldSchema)
|
||||
})
|
||||
typeString += `}`
|
||||
return typeString
|
||||
const selectOptionEnums = createSelectOptions(name, schema)
|
||||
const typeName = toPascalCase(name)
|
||||
const genericArgs = getGenericArgStringWithDefault(schema)
|
||||
const fields = schema
|
||||
.map((fieldSchema: FieldSchema) => createTypeField(name, fieldSchema))
|
||||
.join("\n")
|
||||
|
||||
return `${selectOptionEnums}export type ${typeName}Record${genericArgs} = {
|
||||
${fields}
|
||||
}`
|
||||
}
|
||||
|
||||
export function createResponseType(name: string, schema: Array<FieldSchema>) {
|
||||
export function createResponseType(collectionSchemaEntry: CollectionRecord) {
|
||||
const { name, schema, type } = collectionSchemaEntry
|
||||
const pascaleName = toPascalCase(name)
|
||||
let typeString = `export type ${pascaleName}Response${getGenericArgStringWithDefault(
|
||||
schema
|
||||
)} = ${pascaleName}Record${getGenericArgString(schema)} & BaseRecord`
|
||||
return typeString
|
||||
const genericArgsWithDefaults = getGenericArgStringWithDefault(schema)
|
||||
const genericArgs = getGenericArgString(schema)
|
||||
const systemFields = getSystemFields(type)
|
||||
|
||||
return `export type ${pascaleName}Response${genericArgsWithDefaults} = ${pascaleName}Record${genericArgs} & ${systemFields}`
|
||||
}
|
||||
|
||||
export function createTypeField(fieldSchema: FieldSchema) {
|
||||
export function createTypeField(
|
||||
collectionName: string,
|
||||
fieldSchema: FieldSchema
|
||||
) {
|
||||
if (!(fieldSchema.type in pbSchemaTypescriptMap)) {
|
||||
throw new Error(`unknown type ${fieldSchema.type} found in schema`)
|
||||
}
|
||||
@@ -115,9 +147,26 @@ export function createTypeField(fieldSchema: FieldSchema) {
|
||||
|
||||
const typeString =
|
||||
typeof typeStringOrFunc === "function"
|
||||
? typeStringOrFunc(fieldSchema)
|
||||
? typeStringOrFunc(fieldSchema, collectionName)
|
||||
: typeStringOrFunc
|
||||
return `\t${sanitizeFieldName(fieldSchema.name)}${
|
||||
fieldSchema.required ? "" : "?"
|
||||
}: ${typeString}\n`
|
||||
|
||||
const fieldName = sanitizeFieldName(fieldSchema.name)
|
||||
const required = fieldSchema.required ? "" : "?"
|
||||
|
||||
return `\t${fieldName}${required}: ${typeString}`
|
||||
}
|
||||
|
||||
export function createSelectOptions(
|
||||
recordName: string,
|
||||
schema: Array<FieldSchema>
|
||||
) {
|
||||
const selectFields = schema.filter((field) => field.type === "select")
|
||||
const typestring = selectFields
|
||||
.map(
|
||||
(field) => `export enum ${getOptionEnumName(recordName, field.name)} {
|
||||
${field.options.values?.map((val) => `\t${val} = "${val}",`).join("\n")}
|
||||
}\n`
|
||||
)
|
||||
.join("\n")
|
||||
return typestring
|
||||
}
|
||||
|
||||
@@ -26,15 +26,15 @@ export async function fromJSON(path: string): Promise<Array<CollectionRecord>> {
|
||||
|
||||
export async function fromURL(
|
||||
url: string,
|
||||
email: string = "",
|
||||
password: string = ""
|
||||
email = "",
|
||||
password = ""
|
||||
): Promise<Array<CollectionRecord>> {
|
||||
const formData = new FormData()
|
||||
formData.append("email", email)
|
||||
formData.append("identity", email)
|
||||
formData.append("password", password)
|
||||
|
||||
// Login
|
||||
const { token } = await fetch(`${url}/api/admins/auth-via-email`, {
|
||||
const { token } = await fetch(`${url}/api/admins/auth-with-password`, {
|
||||
method: "post",
|
||||
// @ts-ignore
|
||||
body: formData,
|
||||
@@ -43,7 +43,7 @@ export async function fromURL(
|
||||
// Get the collection
|
||||
const result = await fetch(`${url}/api/collections?perPage=200`, {
|
||||
headers: {
|
||||
Authorization: `Admin ${token}`,
|
||||
Authorization: token,
|
||||
},
|
||||
}).then((res) => res.json())
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ export type FieldSchema = {
|
||||
|
||||
export type CollectionRecord = {
|
||||
id: string
|
||||
type: "base" | "auth"
|
||||
name: string
|
||||
system: boolean
|
||||
listRule: string | null
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CollectionRecord } from "./types"
|
||||
import { promises as fs } from "fs"
|
||||
|
||||
export function toPascalCase(str: string) {
|
||||
@@ -22,3 +23,11 @@ export async function saveFile(outPath: string, typeString: string) {
|
||||
await fs.writeFile(outPath, typeString, "utf8")
|
||||
console.log(`Created typescript definitions at ${outPath}`)
|
||||
}
|
||||
|
||||
export function getSystemFields(type: CollectionRecord["type"]) {
|
||||
return type === "auth" ? "AuthSystemFields" : "BaseSystemFields"
|
||||
}
|
||||
|
||||
export function getOptionEnumName(recordName: string, fieldName: string) {
|
||||
return `${toPascalCase(recordName)}${toPascalCase(fieldName)}Options`
|
||||
}
|
||||
|
||||
96
test/__snapshots__/fromJSON.test.ts.snap
Normal file
96
test/__snapshots__/fromJSON.test.ts.snap
Normal file
@@ -0,0 +1,96 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`creates a type file from json schema 1`] = `
|
||||
"/**
|
||||
* This file was @generated using pocketbase-typegen
|
||||
*/
|
||||
|
||||
export enum Collections {
|
||||
Base = "base",
|
||||
CustomAuth = "custom_auth",
|
||||
Everything = "everything",
|
||||
Posts = "posts",
|
||||
Users = "users",
|
||||
}
|
||||
|
||||
// Alias types for improved usability
|
||||
export type IsoDateString = string
|
||||
export type RecordIdString = string
|
||||
|
||||
// System fields
|
||||
export type BaseSystemFields = {
|
||||
id: RecordIdString
|
||||
created: IsoDateString
|
||||
updated: IsoDateString
|
||||
collectionId: string
|
||||
collectionName: Collections
|
||||
expand?: { [key: string]: any }
|
||||
}
|
||||
|
||||
export type AuthSystemFields = {
|
||||
email: string
|
||||
emailVisibility: boolean
|
||||
username: string
|
||||
verified: boolean
|
||||
} & BaseSystemFields
|
||||
|
||||
// Record types for each collection
|
||||
|
||||
export type BaseRecord = {
|
||||
field?: string
|
||||
}
|
||||
|
||||
export type CustomAuthRecord = {
|
||||
custom_field?: string
|
||||
}
|
||||
|
||||
export enum EverythingSelectFieldOptions {
|
||||
optionA = "optionA",
|
||||
optionB = "optionB",
|
||||
optionC = "optionC",
|
||||
}
|
||||
export type EverythingRecord<Tanother_json_field = unknown, Tjson_field = unknown> = {
|
||||
text_field?: string
|
||||
number_field?: number
|
||||
bool_field?: boolean
|
||||
email_field?: string
|
||||
url_field?: string
|
||||
date_field?: IsoDateString
|
||||
select_field?: EverythingSelectFieldOptions
|
||||
json_field?: null | Tjson_field
|
||||
another_json_field?: null | Tanother_json_field
|
||||
file_field?: string
|
||||
three_files_field?: string[]
|
||||
user_relation_field?: RecordIdString
|
||||
custom_relation_field?: RecordIdString[]
|
||||
post_relation_field?: RecordIdString
|
||||
select_field_no_values?: string
|
||||
}
|
||||
|
||||
export type PostsRecord = {
|
||||
field?: string
|
||||
nonempty_field: string
|
||||
nonempty_bool: boolean
|
||||
field1?: number
|
||||
}
|
||||
|
||||
export type UsersRecord = {
|
||||
name?: string
|
||||
avatar?: string
|
||||
}
|
||||
|
||||
// Response types include system fields and match responses from the PocketBase API
|
||||
export type BaseResponse = BaseRecord & BaseSystemFields
|
||||
export type CustomAuthResponse = CustomAuthRecord & AuthSystemFields
|
||||
export type EverythingResponse<Tanother_json_field = unknown, Tjson_field = unknown> = EverythingRecord<Tanother_json_field, Tjson_field> & BaseSystemFields
|
||||
export type PostsResponse = PostsRecord & BaseSystemFields
|
||||
export type UsersResponse = UsersRecord & AuthSystemFields
|
||||
|
||||
export type CollectionRecords = {
|
||||
base: BaseRecord
|
||||
custom_auth: CustomAuthRecord
|
||||
everything: EverythingRecord
|
||||
posts: PostsRecord
|
||||
users: UsersRecord
|
||||
}"
|
||||
`;
|
||||
@@ -1,65 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`creates a type file from json schema 1`] = `
|
||||
"// This file was @generated using pocketbase-typegen
|
||||
|
||||
export type IsoDateString = string
|
||||
|
||||
export type RecordIdString = string
|
||||
|
||||
export type UserIdString = string
|
||||
|
||||
export type BaseRecord = {
|
||||
id: RecordIdString
|
||||
created: IsoDateString
|
||||
updated: IsoDateString
|
||||
"@collectionId": string
|
||||
"@collectionName": string
|
||||
"@expand"?: { [key: string]: any }
|
||||
}
|
||||
|
||||
export enum Collections {
|
||||
EveryType = "every_type",
|
||||
Orders = "orders",
|
||||
Profiles = "profiles",
|
||||
}
|
||||
|
||||
export type EveryTypeRecord<Tjson_field = unknown> = {
|
||||
text_field: string
|
||||
number_field: number
|
||||
bool_field: boolean
|
||||
email_field?: string
|
||||
url_field?: string
|
||||
date_field?: IsoDateString
|
||||
select_field?: "optionA" | "optionB" | "optionC"
|
||||
json_field?: null | Tjson_field
|
||||
file_field?: string
|
||||
relation_field?: RecordIdString
|
||||
user_field?: UserIdString
|
||||
}
|
||||
|
||||
export type EveryTypeResponse<Tjson_field = unknown> = EveryTypeRecord<Tjson_field> & BaseRecord
|
||||
|
||||
export type OrdersRecord = {
|
||||
amount: number
|
||||
payment_type: "credit card" | "paypal" | "crypto"
|
||||
user: UserIdString
|
||||
product: string
|
||||
}
|
||||
|
||||
export type OrdersResponse = OrdersRecord & BaseRecord
|
||||
|
||||
export type ProfilesRecord = {
|
||||
userId: UserIdString
|
||||
name?: string
|
||||
avatar?: string
|
||||
}
|
||||
|
||||
export type ProfilesResponse = ProfilesRecord & BaseRecord
|
||||
|
||||
export type CollectionRecords = {
|
||||
every_type: EveryTypeRecord
|
||||
orders: OrdersRecord
|
||||
profiles: ProfilesRecord
|
||||
}"
|
||||
`;
|
||||
@@ -26,7 +26,7 @@ exports[`createRecordType handles file fields with multiple files 1`] = `
|
||||
}"
|
||||
`;
|
||||
|
||||
exports[`createResponseType creates type definition for a response 1`] = `"export type BooksResponse = BooksRecord & BaseRecord"`;
|
||||
exports[`createResponseType creates type definition for a response 1`] = `"export type BooksResponse = BooksRecord & BaseSystemFields"`;
|
||||
|
||||
exports[`createResponseType handles file fields with multiple files 1`] = `
|
||||
"export type BooksRecord = {
|
||||
@@ -35,32 +35,43 @@ exports[`createResponseType handles file fields with multiple files 1`] = `
|
||||
`;
|
||||
|
||||
exports[`generate generates correct output given db input 1`] = `
|
||||
"// This file was @generated using pocketbase-typegen
|
||||
|
||||
export type IsoDateString = string
|
||||
|
||||
export type RecordIdString = string
|
||||
|
||||
export type UserIdString = string
|
||||
|
||||
export type BaseRecord = {
|
||||
id: RecordIdString
|
||||
created: IsoDateString
|
||||
updated: IsoDateString
|
||||
"@collectionId": string
|
||||
"@collectionName": string
|
||||
"@expand"?: { [key: string]: any }
|
||||
}
|
||||
"/**
|
||||
* This file was @generated using pocketbase-typegen
|
||||
*/
|
||||
|
||||
export enum Collections {
|
||||
Books = "books",
|
||||
}
|
||||
|
||||
// Alias types for improved usability
|
||||
export type IsoDateString = string
|
||||
export type RecordIdString = string
|
||||
|
||||
// System fields
|
||||
export type BaseSystemFields = {
|
||||
id: RecordIdString
|
||||
created: IsoDateString
|
||||
updated: IsoDateString
|
||||
collectionId: string
|
||||
collectionName: Collections
|
||||
expand?: { [key: string]: any }
|
||||
}
|
||||
|
||||
export type AuthSystemFields = {
|
||||
email: string
|
||||
emailVisibility: boolean
|
||||
username: string
|
||||
verified: boolean
|
||||
} & BaseSystemFields
|
||||
|
||||
// Record types for each collection
|
||||
|
||||
export type BooksRecord = {
|
||||
title?: string
|
||||
}
|
||||
|
||||
export type BooksResponse = BooksRecord & BaseRecord
|
||||
// Response types include system fields and match responses from the PocketBase API
|
||||
export type BooksResponse = BooksRecord & BaseSystemFields
|
||||
|
||||
export type CollectionRecords = {
|
||||
books: BooksRecord
|
||||
|
||||
24
test/integration/integration.js
Normal file
24
test/integration/integration.js
Normal file
@@ -0,0 +1,24 @@
|
||||
import assert from "node:assert"
|
||||
import fs from "fs/promises"
|
||||
|
||||
// Known good types from repo
|
||||
const controlTypes = await fs.readFile("pocketbase-types-example.ts", {
|
||||
encoding: "utf8",
|
||||
})
|
||||
|
||||
async function testCreateFromUrl() {
|
||||
const typesFromUrl = await fs.readFile("output/pocketbase-types-url.ts", {
|
||||
encoding: "utf8",
|
||||
})
|
||||
assert.equal(typesFromUrl, controlTypes)
|
||||
}
|
||||
|
||||
async function testCreateFromDb() {
|
||||
const typesFromDb = await fs.readFile("output/pocketbase-types-db.ts", {
|
||||
encoding: "utf8",
|
||||
})
|
||||
assert.equal(typesFromDb, controlTypes)
|
||||
}
|
||||
|
||||
await testCreateFromUrl()
|
||||
await testCreateFromDb()
|
||||
BIN
test/integration/pb_data/data.db
Normal file
BIN
test/integration/pb_data/data.db
Normal file
Binary file not shown.
14
test/integration/run.sh
Normal file
14
test/integration/run.sh
Normal file
@@ -0,0 +1,14 @@
|
||||
#!/bin/sh
|
||||
echo "Starting integration test."
|
||||
|
||||
# Start pocketbase server
|
||||
/app/pocketbase serve --http=0.0.0.0:8090 &
|
||||
|
||||
echo "Waiting for server to start."
|
||||
while ! nc -z localhost 8090 </dev/null; do sleep 1; done
|
||||
|
||||
node ./dist/index.js --url http://0.0.0.0:8090 --email test@test.com --password testpassword --out output/pocketbase-types-url.ts
|
||||
node ./dist/index.js --db pb_data/data.db --out output/pocketbase-types-db.ts
|
||||
|
||||
node integration.js
|
||||
echo "Integration tests pass"
|
||||
167
test/lib.test.ts
167
test/lib.test.ts
@@ -1,7 +1,7 @@
|
||||
import { CollectionRecord, FieldSchema } from "../src/types"
|
||||
import {
|
||||
createCollectionEnum,
|
||||
createCollectionRecord,
|
||||
createCollectionRecords,
|
||||
createRecordType,
|
||||
createResponseType,
|
||||
createTypeField,
|
||||
@@ -24,6 +24,7 @@ describe("generate", () => {
|
||||
{
|
||||
name: "books",
|
||||
id: "123",
|
||||
type: "base",
|
||||
system: false,
|
||||
listRule: null,
|
||||
viewRule: null,
|
||||
@@ -58,7 +59,7 @@ describe("createCollectionEnum", () => {
|
||||
describe("createCollectionRecord", () => {
|
||||
it("creates mapping of collection name to record type", () => {
|
||||
const names = ["book", "magazine"]
|
||||
expect(createCollectionRecord(names)).toMatchSnapshot()
|
||||
expect(createCollectionRecords(names)).toMatchSnapshot()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -100,19 +101,30 @@ describe("createRecordType", () => {
|
||||
|
||||
describe("createResponseType", () => {
|
||||
it("creates type definition for a response", () => {
|
||||
const name = "books"
|
||||
const schema: FieldSchema[] = [
|
||||
{
|
||||
system: false,
|
||||
id: "hhnwjkke",
|
||||
name: "title",
|
||||
type: "text",
|
||||
required: false,
|
||||
unique: false,
|
||||
options: { min: null, max: null, pattern: "" },
|
||||
},
|
||||
]
|
||||
const result = createResponseType(name, schema)
|
||||
const row: CollectionRecord = {
|
||||
type: "base",
|
||||
id: "123",
|
||||
system: false,
|
||||
listRule: null,
|
||||
viewRule: null,
|
||||
createRule: null,
|
||||
updateRule: null,
|
||||
deleteRule: null,
|
||||
name: "books",
|
||||
schema: [
|
||||
{
|
||||
system: false,
|
||||
id: "hhnwjkke",
|
||||
name: "title",
|
||||
type: "text",
|
||||
required: false,
|
||||
unique: false,
|
||||
options: { min: null, max: null, pattern: "" },
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
const result = createResponseType(row)
|
||||
expect(result).toMatchSnapshot()
|
||||
})
|
||||
|
||||
@@ -137,75 +149,96 @@ describe("createResponseType", () => {
|
||||
describe("createTypeField", () => {
|
||||
it("handles required and optional fields", () => {
|
||||
expect(
|
||||
createTypeField({
|
||||
createTypeField("test_collection", {
|
||||
...defaultFieldSchema,
|
||||
required: false,
|
||||
})
|
||||
).toEqual("\tdefaultName?: string\n")
|
||||
).toEqual("\tdefaultName?: string")
|
||||
expect(
|
||||
createTypeField({
|
||||
createTypeField("test_collection", {
|
||||
...defaultFieldSchema,
|
||||
required: true,
|
||||
})
|
||||
).toEqual("\tdefaultName: string\n")
|
||||
).toEqual("\tdefaultName: string")
|
||||
})
|
||||
|
||||
it("converts pocketbase schema types to typescript", () => {
|
||||
it("converts default types to typescript", () => {
|
||||
expect(
|
||||
createTypeField({
|
||||
createTypeField("test_collection", {
|
||||
...defaultFieldSchema,
|
||||
})
|
||||
).toEqual("\tdefaultName: string\n")
|
||||
).toEqual("\tdefaultName: string")
|
||||
expect(
|
||||
createTypeField({
|
||||
createTypeField("test_collection", {
|
||||
...defaultFieldSchema,
|
||||
name: "textField",
|
||||
})
|
||||
).toEqual("\ttextField: string\n")
|
||||
).toEqual("\ttextField: string")
|
||||
})
|
||||
|
||||
it("converts number type", () => {
|
||||
expect(
|
||||
createTypeField({
|
||||
createTypeField("test_collection", {
|
||||
...defaultFieldSchema,
|
||||
name: "numberField",
|
||||
type: "number",
|
||||
})
|
||||
).toEqual("\tnumberField: number\n")
|
||||
).toEqual("\tnumberField: number")
|
||||
})
|
||||
|
||||
it("converts bool type", () => {
|
||||
expect(
|
||||
createTypeField({
|
||||
createTypeField("test_collection", {
|
||||
...defaultFieldSchema,
|
||||
name: "boolField",
|
||||
type: "bool",
|
||||
})
|
||||
).toEqual("\tboolField: boolean\n")
|
||||
).toEqual("\tboolField: boolean")
|
||||
})
|
||||
|
||||
it("converts email type", () => {
|
||||
expect(
|
||||
createTypeField({
|
||||
createTypeField("test_collection", {
|
||||
...defaultFieldSchema,
|
||||
name: "emailField",
|
||||
type: "email",
|
||||
})
|
||||
).toEqual("\temailField: string\n")
|
||||
).toEqual("\temailField: string")
|
||||
})
|
||||
|
||||
it("converts url type", () => {
|
||||
expect(
|
||||
createTypeField({
|
||||
createTypeField("test_collection", {
|
||||
...defaultFieldSchema,
|
||||
name: "urlField",
|
||||
type: "url",
|
||||
})
|
||||
).toEqual("\turlField: string\n")
|
||||
).toEqual("\turlField: string")
|
||||
})
|
||||
|
||||
it("converts date type", () => {
|
||||
expect(
|
||||
createTypeField({
|
||||
createTypeField("test_collection", {
|
||||
...defaultFieldSchema,
|
||||
name: "dateField",
|
||||
type: "date",
|
||||
})
|
||||
).toEqual("\tdateField: IsoDateString\n")
|
||||
).toEqual("\tdateField: IsoDateString")
|
||||
})
|
||||
|
||||
it("converts select type", () => {
|
||||
expect(
|
||||
createTypeField({
|
||||
createTypeField("test_collection", {
|
||||
...defaultFieldSchema,
|
||||
name: "selectField",
|
||||
type: "select",
|
||||
})
|
||||
).toEqual("\tselectField: string\n")
|
||||
).toEqual("\tselectField: string")
|
||||
})
|
||||
|
||||
it("converts select type with value", () => {
|
||||
expect(
|
||||
createTypeField({
|
||||
createTypeField("test_collection", {
|
||||
...defaultFieldSchema,
|
||||
name: "selectFieldWithOpts",
|
||||
type: "select",
|
||||
@@ -213,23 +246,32 @@ describe("createTypeField", () => {
|
||||
values: ["one", "two", "three"],
|
||||
},
|
||||
})
|
||||
).toEqual(`\tselectFieldWithOpts: "one" | "two" | "three"\n`)
|
||||
).toEqual(`\tselectFieldWithOpts: TestCollectionSelectFieldWithOptsOptions`)
|
||||
})
|
||||
|
||||
it("converts json type", () => {
|
||||
expect(
|
||||
createTypeField({
|
||||
createTypeField("test_collection", {
|
||||
...defaultFieldSchema,
|
||||
name: "jsonField",
|
||||
type: "json",
|
||||
})
|
||||
).toEqual("\tjsonField: null | TjsonField\n")
|
||||
).toEqual("\tjsonField: null | TjsonField")
|
||||
})
|
||||
|
||||
it("converts file type", () => {
|
||||
expect(
|
||||
createTypeField({
|
||||
createTypeField("test_collection", {
|
||||
...defaultFieldSchema,
|
||||
name: "fileField",
|
||||
type: "file",
|
||||
})
|
||||
).toEqual("\tfileField: string\n")
|
||||
).toEqual("\tfileField: string")
|
||||
})
|
||||
|
||||
it("converts file type with multiple files", () => {
|
||||
expect(
|
||||
createTypeField({
|
||||
createTypeField("test_collection", {
|
||||
...defaultFieldSchema,
|
||||
name: "fileField",
|
||||
type: "file",
|
||||
@@ -237,27 +279,50 @@ describe("createTypeField", () => {
|
||||
maxSelect: 3,
|
||||
},
|
||||
})
|
||||
).toEqual("\tfileField: string[]\n")
|
||||
).toEqual("\tfileField: string[]")
|
||||
})
|
||||
|
||||
it("converts relation type", () => {
|
||||
expect(
|
||||
createTypeField({
|
||||
createTypeField("test_collection", {
|
||||
...defaultFieldSchema,
|
||||
name: "relationField",
|
||||
type: "relation",
|
||||
})
|
||||
).toEqual("\trelationField: RecordIdString\n")
|
||||
).toEqual("\trelationField: RecordIdString")
|
||||
})
|
||||
|
||||
it("converts relation type with multiple options", () => {
|
||||
expect(
|
||||
createTypeField({
|
||||
createTypeField("test_collection", {
|
||||
...defaultFieldSchema,
|
||||
name: "userField",
|
||||
name: "relationFieldMany",
|
||||
type: "relation",
|
||||
options: {
|
||||
maxSelect: 3,
|
||||
},
|
||||
})
|
||||
).toEqual("\trelationFieldMany: RecordIdString[]")
|
||||
})
|
||||
|
||||
// DEPRECATED: This was removed in PocketBase v0.8
|
||||
it("converts user relation type", () => {
|
||||
expect(
|
||||
createTypeField("test_collection", {
|
||||
...defaultFieldSchema,
|
||||
name: "userRelationField",
|
||||
type: "user",
|
||||
})
|
||||
).toEqual("\tuserField: UserIdString\n")
|
||||
).toEqual("\tuserRelationField: RecordIdString")
|
||||
})
|
||||
|
||||
it("throws for unexpected types", () => {
|
||||
expect(() =>
|
||||
// @ts-ignore
|
||||
createTypeField({ ...defaultFieldSchema, type: "unknowntype" })
|
||||
createTypeField("test_collection", {
|
||||
...defaultFieldSchema,
|
||||
// @ts-ignore
|
||||
type: "unknowntype",
|
||||
})
|
||||
).toThrowError("unknown type unknowntype found in schema")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,28 +1,12 @@
|
||||
[
|
||||
{
|
||||
"id": "systemprofiles0",
|
||||
"name": "profiles",
|
||||
"system": true,
|
||||
"listRule": "userId = @request.user.id",
|
||||
"viewRule": "userId = @request.user.id",
|
||||
"createRule": "userId = @request.user.id",
|
||||
"updateRule": "userId = @request.user.id",
|
||||
"deleteRule": null,
|
||||
"id": "_pb_users_auth_",
|
||||
"name": "users",
|
||||
"type": "auth",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"id": "pbfielduser",
|
||||
"name": "userId",
|
||||
"type": "user",
|
||||
"system": true,
|
||||
"required": true,
|
||||
"unique": true,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"cascadeDelete": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pbfieldname",
|
||||
"id": "users_name",
|
||||
"name": "name",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
@@ -35,7 +19,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pbfieldavatar",
|
||||
"id": "users_avatar",
|
||||
"name": "avatar",
|
||||
"type": "file",
|
||||
"system": false,
|
||||
@@ -54,24 +38,35 @@
|
||||
"thumbs": null
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"listRule": "id = @request.auth.id",
|
||||
"viewRule": "id = @request.auth.id",
|
||||
"createRule": "",
|
||||
"updateRule": "id = @request.auth.id",
|
||||
"deleteRule": "id = @request.auth.id",
|
||||
"options": {
|
||||
"allowEmailAuth": true,
|
||||
"allowOAuth2Auth": true,
|
||||
"allowUsernameAuth": true,
|
||||
"exceptEmailDomains": null,
|
||||
"manageRule": null,
|
||||
"minPasswordLength": 8,
|
||||
"onlyEmailDomains": null,
|
||||
"requireEmail": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "l9oq1jy97be69be",
|
||||
"name": "every_type",
|
||||
"id": "8uexthr74u6jat4",
|
||||
"name": "everything",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"schema": [
|
||||
{
|
||||
"id": "locu2fqr",
|
||||
"id": "ze7zu2ji",
|
||||
"name": "text_field",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"required": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
@@ -80,11 +75,11 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "4pqdzxck",
|
||||
"id": "6chpapqa",
|
||||
"name": "number_field",
|
||||
"type": "number",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"required": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
@@ -92,28 +87,28 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "xtjqakgy",
|
||||
"id": "bunghw2b",
|
||||
"name": "bool_field",
|
||||
"type": "bool",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"required": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "dc9wxgad",
|
||||
"id": "kgt2vwcr",
|
||||
"name": "email_field",
|
||||
"type": "email",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"exceptDomains": null,
|
||||
"onlyDomains": null
|
||||
"exceptDomains": [],
|
||||
"onlyDomains": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "ptemvsvm",
|
||||
"id": "pbyqwc6g",
|
||||
"name": "url_field",
|
||||
"type": "url",
|
||||
"system": false,
|
||||
@@ -125,7 +120,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "6reugpzq",
|
||||
"id": "erxbavbq",
|
||||
"name": "date_field",
|
||||
"type": "date",
|
||||
"system": false,
|
||||
@@ -137,7 +132,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "5tsmp1az",
|
||||
"id": "hy5g988n",
|
||||
"name": "select_field",
|
||||
"type": "select",
|
||||
"system": false,
|
||||
@@ -149,7 +144,7 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "jo91e9vw",
|
||||
"id": "pbwoyo77",
|
||||
"name": "json_field",
|
||||
"type": "json",
|
||||
"system": false,
|
||||
@@ -158,7 +153,16 @@
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "f2x5ly7x",
|
||||
"id": "balhjgn8",
|
||||
"name": "another_json_field",
|
||||
"type": "json",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "cdblcmro",
|
||||
"name": "file_field",
|
||||
"type": "file",
|
||||
"system": false,
|
||||
@@ -172,81 +176,101 @@
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "uky0rgym",
|
||||
"name": "relation_field",
|
||||
"id": "uxeyxkfd",
|
||||
"name": "three_files_field",
|
||||
"type": "file",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 3,
|
||||
"maxSize": 5242880,
|
||||
"mimeTypes": [],
|
||||
"thumbs": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "vyuzrvxm",
|
||||
"name": "user_relation_field",
|
||||
"type": "relation",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"collectionId": "dkrwccg04gaf6n0",
|
||||
"collectionId": "_pb_users_auth_",
|
||||
"cascadeDelete": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "qerbl31d",
|
||||
"name": "user_field",
|
||||
"type": "user",
|
||||
"id": "fjzpmh9i",
|
||||
"name": "custom_relation_field",
|
||||
"type": "relation",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 5,
|
||||
"collectionId": "rs7hepu8zl6kr8e",
|
||||
"cascadeDelete": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "iwh5jvyg",
|
||||
"name": "post_relation_field",
|
||||
"type": "relation",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"collectionId": "z6b9mssubo9megi",
|
||||
"cascadeDelete": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "tccaq6g6",
|
||||
"name": "select_field_no_values",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "dkrwccg04gaf6n0",
|
||||
"name": "orders",
|
||||
"system": false,
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "z6b9mssubo9megi",
|
||||
"name": "posts",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"id": "fbipzebf",
|
||||
"name": "amount",
|
||||
"type": "number",
|
||||
"id": "wzasqdgc",
|
||||
"name": "field",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"required": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "gvuptuxz",
|
||||
"name": "payment_type",
|
||||
"type": "select",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"values": ["credit card", "paypal", "crypto"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "bnji5emw",
|
||||
"name": "user",
|
||||
"type": "user",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"maxSelect": 1,
|
||||
"cascadeDelete": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "pfelzqqv",
|
||||
"name": "product",
|
||||
"id": "175adqww",
|
||||
"name": "nonempty_field",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": true,
|
||||
@@ -256,7 +280,97 @@
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "s3cl0rdp",
|
||||
"name": "nonempty_bool",
|
||||
"type": "bool",
|
||||
"system": false,
|
||||
"required": true,
|
||||
"unique": false,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "36buozcb",
|
||||
"name": "field1",
|
||||
"type": "number",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
},
|
||||
{
|
||||
"id": "rs7hepu8zl6kr8e",
|
||||
"name": "custom_auth",
|
||||
"type": "auth",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"id": "zj6cku68",
|
||||
"name": "custom_field",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {
|
||||
"allowEmailAuth": true,
|
||||
"allowOAuth2Auth": true,
|
||||
"allowUsernameAuth": true,
|
||||
"exceptEmailDomains": null,
|
||||
"manageRule": null,
|
||||
"minPasswordLength": 8,
|
||||
"onlyEmailDomains": null,
|
||||
"requireEmail": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "kr8109mcfuu18qq",
|
||||
"name": "base",
|
||||
"type": "base",
|
||||
"system": false,
|
||||
"schema": [
|
||||
{
|
||||
"id": "epgo3hyb",
|
||||
"name": "field",
|
||||
"type": "text",
|
||||
"system": false,
|
||||
"required": false,
|
||||
"unique": false,
|
||||
"options": {
|
||||
"min": null,
|
||||
"max": null,
|
||||
"pattern": ""
|
||||
}
|
||||
}
|
||||
],
|
||||
"listRule": null,
|
||||
"viewRule": null,
|
||||
"createRule": null,
|
||||
"updateRule": null,
|
||||
"deleteRule": null,
|
||||
"options": {}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,61 +1,92 @@
|
||||
// This file was @generated using pocketbase-typegen
|
||||
/**
|
||||
* This file was @generated using pocketbase-typegen
|
||||
*/
|
||||
|
||||
export enum Collections {
|
||||
Base = "base",
|
||||
CustomAuth = "custom_auth",
|
||||
Everything = "everything",
|
||||
Posts = "posts",
|
||||
Users = "users",
|
||||
}
|
||||
|
||||
// Alias types for improved usability
|
||||
export type IsoDateString = string
|
||||
|
||||
export type RecordIdString = string
|
||||
|
||||
export type UserIdString = string
|
||||
|
||||
export type BaseRecord = {
|
||||
// System fields
|
||||
export type BaseSystemFields = {
|
||||
id: RecordIdString
|
||||
created: IsoDateString
|
||||
updated: IsoDateString
|
||||
"@collectionId": string
|
||||
"@collectionName": string
|
||||
"@expand"?: { [key: string]: any }
|
||||
collectionId: string
|
||||
collectionName: Collections
|
||||
expand?: { [key: string]: any }
|
||||
}
|
||||
|
||||
export enum Collections {
|
||||
EveryType = "every_type",
|
||||
Orders = "orders",
|
||||
Profiles = "profiles",
|
||||
export type AuthSystemFields = {
|
||||
email: string
|
||||
emailVisibility: boolean
|
||||
username: string
|
||||
verified: boolean
|
||||
} & BaseSystemFields
|
||||
|
||||
// Record types for each collection
|
||||
|
||||
export type BaseRecord = {
|
||||
field?: string
|
||||
}
|
||||
|
||||
export type EveryTypeRecord<Tjson_field = unknown> = {
|
||||
text_field: string
|
||||
number_field: number
|
||||
bool_field: boolean
|
||||
export type CustomAuthRecord = {
|
||||
custom_field?: string
|
||||
}
|
||||
|
||||
export enum EverythingSelectFieldOptions {
|
||||
optionA = "optionA",
|
||||
optionB = "optionB",
|
||||
optionC = "optionC",
|
||||
}
|
||||
export type EverythingRecord<Tanother_json_field = unknown, Tjson_field = unknown> = {
|
||||
text_field?: string
|
||||
number_field?: number
|
||||
bool_field?: boolean
|
||||
email_field?: string
|
||||
url_field?: string
|
||||
date_field?: IsoDateString
|
||||
select_field?: "optionA" | "optionB" | "optionC"
|
||||
select_field?: EverythingSelectFieldOptions
|
||||
json_field?: null | Tjson_field
|
||||
another_json_field?: null | Tanother_json_field
|
||||
file_field?: string
|
||||
relation_field?: RecordIdString
|
||||
user_field?: UserIdString
|
||||
three_files_field?: string[]
|
||||
user_relation_field?: RecordIdString
|
||||
custom_relation_field?: RecordIdString[]
|
||||
post_relation_field?: RecordIdString
|
||||
select_field_no_values?: string
|
||||
}
|
||||
|
||||
export type EveryTypeResponse<Tjson_field = unknown> = EveryTypeRecord<Tjson_field> & BaseRecord
|
||||
|
||||
export type OrdersRecord = {
|
||||
amount: number
|
||||
payment_type: "credit card" | "paypal" | "crypto"
|
||||
user: UserIdString
|
||||
product: string
|
||||
export type PostsRecord = {
|
||||
field?: string
|
||||
nonempty_field: string
|
||||
nonempty_bool: boolean
|
||||
field1?: number
|
||||
}
|
||||
|
||||
export type OrdersResponse = OrdersRecord & BaseRecord
|
||||
|
||||
export type ProfilesRecord = {
|
||||
userId: UserIdString
|
||||
export type UsersRecord = {
|
||||
name?: string
|
||||
avatar?: string
|
||||
}
|
||||
|
||||
export type ProfilesResponse = ProfilesRecord & BaseRecord
|
||||
// Response types include system fields and match responses from the PocketBase API
|
||||
export type BaseResponse = BaseRecord & BaseSystemFields
|
||||
export type CustomAuthResponse = CustomAuthRecord & AuthSystemFields
|
||||
export type EverythingResponse<Tanother_json_field = unknown, Tjson_field = unknown> = EverythingRecord<Tanother_json_field, Tjson_field> & BaseSystemFields
|
||||
export type PostsResponse = PostsRecord & BaseSystemFields
|
||||
export type UsersResponse = UsersRecord & AuthSystemFields
|
||||
|
||||
export type CollectionRecords = {
|
||||
every_type: EveryTypeRecord
|
||||
orders: OrdersRecord
|
||||
profiles: ProfilesRecord
|
||||
base: BaseRecord
|
||||
custom_auth: CustomAuthRecord
|
||||
everything: EverythingRecord
|
||||
posts: PostsRecord
|
||||
users: UsersRecord
|
||||
}
|
||||
@@ -5,7 +5,8 @@
|
||||
import {
|
||||
CollectionRecords,
|
||||
Collections,
|
||||
EveryTypeRecord,
|
||||
EverythingRecord,
|
||||
EverythingSelectFieldOptions,
|
||||
} from "./pocketbase-types-example"
|
||||
|
||||
// Utility function can to infer collection type
|
||||
@@ -13,26 +14,37 @@ function getOne<T extends keyof CollectionRecords>(
|
||||
collection: T,
|
||||
id: string
|
||||
): CollectionRecords[T] {
|
||||
console.log(collection, id)
|
||||
return JSON.parse("id") as CollectionRecords[T]
|
||||
}
|
||||
|
||||
// Return type is correctly inferred
|
||||
let thing = getOne(Collections.EveryType, "a")
|
||||
const thing = getOne(Collections.Everything, "a")
|
||||
|
||||
// Works when passing in JSON generic
|
||||
const everythingRecordWithGeneric: EveryTypeRecord<{ a: "some string" }> = {
|
||||
const everythingRecordWithGeneric: EverythingRecord<{ a: "some string" }> = {
|
||||
json_field: { a: "some string" },
|
||||
text_field: "string",
|
||||
number_field: 1,
|
||||
bool_field: true,
|
||||
}
|
||||
|
||||
// Works without passing in JSON generic
|
||||
const everythingRecordWithoutGeneric: EveryTypeRecord = {
|
||||
const everythingRecordWithoutGeneric: EverythingRecord = {
|
||||
json_field: { a: "some string" },
|
||||
text_field: "string",
|
||||
number_field: 1,
|
||||
bool_field: true,
|
||||
}
|
||||
|
||||
console.log(thing, everythingRecordWithGeneric, everythingRecordWithoutGeneric)
|
||||
// Test select option enums
|
||||
const selectOptions: EverythingRecord = {
|
||||
select_field: EverythingSelectFieldOptions.optionA,
|
||||
select_field_no_values: "foo",
|
||||
}
|
||||
|
||||
// Reference the created variables
|
||||
console.log(
|
||||
thing,
|
||||
everythingRecordWithGeneric,
|
||||
everythingRecordWithoutGeneric,
|
||||
selectOptions
|
||||
)
|
||||
|
||||
@@ -1,4 +1,9 @@
|
||||
import { sanitizeFieldName, toPascalCase } from "../src/utils"
|
||||
import {
|
||||
getOptionEnumName,
|
||||
getSystemFields,
|
||||
sanitizeFieldName,
|
||||
toPascalCase,
|
||||
} from "../src/utils"
|
||||
|
||||
describe("toPascalCase", () => {
|
||||
it("return pascal case string", () => {
|
||||
@@ -22,3 +27,19 @@ describe("sanitizeFieldName", () => {
|
||||
expect(sanitizeFieldName("4number")).toEqual('"4number"')
|
||||
})
|
||||
})
|
||||
|
||||
describe("getSystemFields", () => {
|
||||
it("returns the system field type name for a given collection type", () => {
|
||||
expect(getSystemFields("base")).toBe("BaseSystemFields")
|
||||
expect(getSystemFields("auth")).toBe("AuthSystemFields")
|
||||
})
|
||||
})
|
||||
|
||||
describe("getOptionEnumName", () => {
|
||||
it("returns the enum name for select field options", () => {
|
||||
expect(getOptionEnumName("orders", "type")).toBe("OrdersTypeOptions")
|
||||
expect(getOptionEnumName("orders_with_underscore", "type_underscore")).toBe(
|
||||
"OrdersWithUnderscoreTypeUnderscoreOptions"
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user