Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add generation types for Create and Update requests #111

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 122 additions & 1 deletion dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ var EXPORT_COMMENT = `/**
var IMPORTS = `import type PocketBase from 'pocketbase'
import type { RecordService } from 'pocketbase'`;
var RECORD_TYPE_COMMENT = `// Record types for each collection`;
var CREATE_TYPE_COMMENT = `// Create types for each collection`;
var UPDATE_TYPE_COMMENT = `// Update types for each collection`;
var RESPONSE_TYPE_COMMENT = `// Response types include system fields and match responses from the PocketBase API`;
var ALL_RECORD_RESPONSE_COMMENT = `// Types containing all Records and Responses, useful for creating typing helper functions`;
var TYPED_POCKETBASE_COMMENT = `// Type for usage with type asserted PocketBase instance
Expand All @@ -76,20 +78,42 @@ var HTML_STRING_NAME = `HTMLString`;
var ALIAS_TYPE_DEFINITIONS = `// Alias types for improved usability
export type ${DATE_STRING_TYPE_NAME} = string
export type ${RECORD_ID_STRING_NAME} = string
export type ${HTML_STRING_NAME} = string`;
export type ${HTML_STRING_NAME} = string
export type Nullable<T> = T | null | ''`;
var NOT_COMMON_COLLECTIONS = ["_authOrigins", "_externalAuths", "_mfas", "_otps"];
var BASE_SYSTEM_FIELDS_DEFINITION = `// System fields
export type BaseSystemFields<T = never> = {
id: ${RECORD_ID_STRING_NAME}
collectionId: string
collectionName: Collections
expand?: T
}`;
var BASE_SYSTEM_CREATE_FIELDS_DEFINITION = `export type BaseSystemCreateFields = {
id?: ${RECORD_ID_STRING_NAME}
}`;
var BASE_SYSTEM_UPDATE_FIELDS_DEFINITION = `export type BaseSystemUpdateFields = unknown`;
var AUTH_SYSTEM_FIELDS_DEFINITION = `export type AuthSystemFields<T = never> = {
email: string
emailVisibility: boolean
username: string
verified: boolean
} & BaseSystemFields<T>`;
var AUTH_SYSTEM_CREATE_FIELDS_DEFINITION = `export type AuthSystemCreateFields = {
id?: ${RECORD_ID_STRING_NAME}
email: string
emailVisibility?: boolean
password: string
passwordConfirm: string
verified?: boolean
}`;
var AUTH_SYSTEM_UPDATE_FIELDS_DEFINITION = `export type AuthSystemUpdateFields = {
email?: string
emailVisibility?: boolean
oldPassword?: string
password?: string
passwordConfirm?: string
verified?: boolean
}`;

// src/utils.ts
import { promises as fs2 } from "fs";
Expand Down Expand Up @@ -117,6 +141,22 @@ function getSystemFields(type) {
return "BaseSystemFields";
}
}
function getSystemCreateFields(type) {
switch (type) {
case "auth":
return "AuthSystemCreateFields";
default:
return "BaseSystemCreateFields";
}
}
function getSystemUpdateFields(type) {
switch (type) {
case "auth":
return "AuthSystemUpdateFields";
default:
return "BaseSystemUpdateFields";
}
}
function getOptionEnumName(recordName, fieldName) {
return `${toPascalCase(recordName)}${toPascalCase(fieldName)}Options`;
}
Expand All @@ -141,6 +181,18 @@ function createCollectionRecords(collectionNames) {
${nameRecordMap}
}`;
}
function createCollectionCreates(collectionNames) {
const nameRecordMap = collectionNames.filter((name) => !NOT_COMMON_COLLECTIONS.includes(name)).map((name) => ` ${name}: ${toPascalCase(name)}Create`).join("\n");
return `export type CollectionCreates = {
${nameRecordMap}
}`;
}
function createCollectionUpdates(collectionNames) {
const nameRecordMap = collectionNames.filter((name) => !NOT_COMMON_COLLECTIONS.includes(name)).map((name) => ` ${name}: ${toPascalCase(name)}Update`).join("\n");
return `export type CollectionUpdates = {
${nameRecordMap}
}`;
}
function createCollectionResponses(collectionNames) {
const nameRecordMap = collectionNames.map((name) => ` ${name}: ${toPascalCase(name)}Response`).join("\n");
return `export type CollectionResponses = {
Expand Down Expand Up @@ -194,6 +246,8 @@ var pbSchemaTypescriptMap = {
password: "string",
number: "number",
file: (fieldSchema) => fieldSchema.maxSelect && fieldSchema.maxSelect > 1 ? "string[]" : "string",
file_create: (fieldSchema) => fieldSchema.maxSelect && fieldSchema.maxSelect > 1 ? "File[]" : "File",
file_update: (fieldSchema) => fieldSchema.maxSelect && fieldSchema.maxSelect > 1 ? "Nullable<File[]>" : "Nullable<File>",
json: (fieldSchema) => `null | ${fieldNameToGeneric(fieldSchema.name)}`,
relation: (fieldSchema) => fieldSchema.maxSelect && fieldSchema.maxSelect === 1 ? RECORD_ID_STRING_NAME : `${RECORD_ID_STRING_NAME}[]`,
select: (fieldSchema, collectionName) => {
Expand All @@ -215,6 +269,33 @@ function createTypeField(collectionName, fieldSchema) {
const required = fieldSchema.required ? "" : "?";
return ` ${fieldName}${required}: ${typeString}`;
}
function createTypeCreateField(collectionName, fieldSchema) {
let typeStringOrFunc;
const fieldType = fieldSchema.type === "file" ? "file_create" : fieldSchema.type;
if (!(fieldType in pbSchemaTypescriptMap)) {
console.log(`WARNING: unknown type "${fieldType}" found in schema`);
typeStringOrFunc = "unknown";
} else {
typeStringOrFunc = pbSchemaTypescriptMap[fieldType];
}
const typeString = typeof typeStringOrFunc === "function" ? typeStringOrFunc(fieldSchema, collectionName) : typeStringOrFunc;
const fieldName = sanitizeFieldName(fieldSchema.name);
const required = fieldSchema.required ? "" : "?";
return ` ${fieldName}${required}: ${typeString}`;
}
function createTypeUpdateField(collectionName, fieldSchema) {
let typeStringOrFunc;
const fieldType = fieldSchema.type === "file" ? "file_update" : fieldSchema.type;
if (!(fieldType in pbSchemaTypescriptMap)) {
console.log(`WARNING: unknown type "${fieldType}" found in schema`);
typeStringOrFunc = "unknown";
} else {
typeStringOrFunc = pbSchemaTypescriptMap[fieldType];
}
const typeString = typeof typeStringOrFunc === "function" ? typeStringOrFunc(fieldSchema, collectionName) : typeStringOrFunc;
const fieldName = sanitizeFieldName(fieldSchema.name);
return ` ${fieldName}?: ${typeString}`;
}
function createSelectOptions(recordName, fields) {
const selectFields = fields.filter((field) => field.type === "select");
const typestring = selectFields.map(
Expand All @@ -237,12 +318,18 @@ function getSelectOptionEnumName(val) {
function generate(results, options2) {
const collectionNames = [];
const recordTypes = [];
const createTypes = [];
const updateTypes = [];
const responseTypes = [RESPONSE_TYPE_COMMENT];
results.sort((a, b) => a.name <= b.name ? -1 : 1).forEach((row) => {
if (row.name)
collectionNames.push(row.name);
if (row.fields) {
recordTypes.push(createRecordType(row.name, row.fields));
if (!NOT_COMMON_COLLECTIONS.includes(row.name)) {
createTypes.push(createCreateType(row));
updateTypes.push(createUpdateType(row));
}
responseTypes.push(createResponseType(row));
}
});
Expand All @@ -253,12 +340,22 @@ function generate(results, options2) {
createCollectionEnum(sortedCollectionNames),
ALIAS_TYPE_DEFINITIONS,
BASE_SYSTEM_FIELDS_DEFINITION,
BASE_SYSTEM_CREATE_FIELDS_DEFINITION,
BASE_SYSTEM_UPDATE_FIELDS_DEFINITION,
AUTH_SYSTEM_FIELDS_DEFINITION,
AUTH_SYSTEM_CREATE_FIELDS_DEFINITION,
AUTH_SYSTEM_UPDATE_FIELDS_DEFINITION,
RECORD_TYPE_COMMENT,
...recordTypes,
CREATE_TYPE_COMMENT,
...createTypes,
UPDATE_TYPE_COMMENT,
...updateTypes,
responseTypes.join("\n"),
ALL_RECORD_RESPONSE_COMMENT,
createCollectionRecords(sortedCollectionNames),
createCollectionCreates(sortedCollectionNames),
createCollectionUpdates(sortedCollectionNames),
createCollectionResponses(sortedCollectionNames),
options2.sdk && TYPED_POCKETBASE_COMMENT,
options2.sdk && createTypedPocketbase(sortedCollectionNames)
Expand All @@ -276,6 +373,30 @@ function createRecordType(name, schema) {
${fields}
}` : "never"}`;
}
function createCreateType(collectionSchemaEntry) {
const { name, fields, type } = collectionSchemaEntry;
const typeName = toPascalCase(name);
const genericArgs = getGenericArgStringWithDefault(fields, {
includeExpand: false
});
const systemFields = getSystemCreateFields(type);
const collectionFields = fields.filter((fieldSchema) => !fieldSchema.system).map((fieldSchema) => createTypeCreateField(name, fieldSchema)).sort().join("\n");
return `export type ${typeName}Create${genericArgs} = ${collectionFields ? `{
${collectionFields}
} & ${systemFields}` : systemFields}`;
}
function createUpdateType(collectionSchemaEntry) {
const { name, fields, type } = collectionSchemaEntry;
const typeName = toPascalCase(name);
const genericArgs = getGenericArgStringWithDefault(fields, {
includeExpand: false
});
const systemFields = getSystemUpdateFields(type);
const collectionFields = fields.filter((fieldSchema) => !fieldSchema.system).map((fieldSchema) => createTypeUpdateField(name, fieldSchema)).sort().join("\n");
return `export type ${typeName}Update${genericArgs} = ${collectionFields ? `{
${collectionFields}
} & ${systemFields}` : systemFields}`;
}
function createResponseType(collectionSchemaEntry) {
const { name, fields, type } = collectionSchemaEntry;
const pascaleName = toPascalCase(name);
Expand Down
25 changes: 25 additions & 0 deletions src/collections.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { NOT_COMMON_COLLECTIONS } from "./constants"
import { toPascalCase } from "./utils"

export function createCollectionEnum(collectionNames: Array<string>): string {
Expand All @@ -21,6 +22,30 @@ ${nameRecordMap}
}`
}

export function createCollectionCreates(
collectionNames: Array<string>
): string {
const nameRecordMap = collectionNames
.filter((name) => !NOT_COMMON_COLLECTIONS.includes(name))
.map((name) => `\t${name}: ${toPascalCase(name)}Create`)
.join("\n")
return `export type CollectionCreates = {
${nameRecordMap}
}`
}

export function createCollectionUpdates(
collectionNames: Array<string>
): string {
const nameRecordMap = collectionNames
.filter((name) => !NOT_COMMON_COLLECTIONS.includes(name))
.map((name) => `\t${name}: ${toPascalCase(name)}Update`)
.join("\n")
return `export type CollectionUpdates = {
${nameRecordMap}
}`
}

export function createCollectionResponses(
collectionNames: Array<string>
): string {
Expand Down
30 changes: 29 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ export const EXPORT_COMMENT = `/**
export const IMPORTS = `import type PocketBase from 'pocketbase'
import type { RecordService } from 'pocketbase'`
export const RECORD_TYPE_COMMENT = `// Record types for each collection`
export const CREATE_TYPE_COMMENT = `// Create types for each collection`
export const UPDATE_TYPE_COMMENT = `// Update types for each collection`
export const RESPONSE_TYPE_COMMENT = `// Response types include system fields and match responses from the PocketBase API`
export const ALL_RECORD_RESPONSE_COMMENT = `// Types containing all Records and Responses, useful for creating typing helper functions`
export const TYPED_POCKETBASE_COMMENT = `// Type for usage with type asserted PocketBase instance\n// https://github.com/pocketbase/js-sdk#specify-typescript-definitions`
Expand All @@ -14,7 +16,9 @@ export const HTML_STRING_NAME = `HTMLString`
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 type ${HTML_STRING_NAME} = string`
export type ${HTML_STRING_NAME} = string
export type Nullable<T> = T | null | ''`
export const NOT_COMMON_COLLECTIONS = ['_authOrigins', '_externalAuths', '_mfas', '_otps']

export const BASE_SYSTEM_FIELDS_DEFINITION = `// System fields
export type BaseSystemFields<T = never> = {
Expand All @@ -24,9 +28,33 @@ export type BaseSystemFields<T = never> = {
\texpand?: T
}`

export const BASE_SYSTEM_CREATE_FIELDS_DEFINITION = `export type BaseSystemCreateFields = {
\tid?: ${RECORD_ID_STRING_NAME}
}`

export const BASE_SYSTEM_UPDATE_FIELDS_DEFINITION = `export type BaseSystemUpdateFields = unknown`

export const AUTH_SYSTEM_FIELDS_DEFINITION = `export type AuthSystemFields<T = never> = {
\temail: string
\temailVisibility: boolean
\tusername: string
\tverified: boolean
} & BaseSystemFields<T>`

export const AUTH_SYSTEM_CREATE_FIELDS_DEFINITION = `export type AuthSystemCreateFields = {
\tid?: ${RECORD_ID_STRING_NAME}
\temail: string
\temailVisibility?: boolean
\tpassword: string
\tpasswordConfirm: string
\tverified?: boolean
}`

export const AUTH_SYSTEM_UPDATE_FIELDS_DEFINITION = `export type AuthSystemUpdateFields = {
\temail?: string
\temailVisibility?: boolean
\toldPassword?: string
\tpassword?: string
\tpasswordConfirm?: string
\tverified?: boolean
}`
65 changes: 64 additions & 1 deletion src/fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {
} from "./constants"
import { getOptionEnumName, getOptionValues, sanitizeFieldName } from "./utils"

import { FieldSchema } from "./types"
import { fieldNameToGeneric } from "./generics"
import { FieldSchema } from "./types"

/**
* Convert the pocketbase field type to the equivalent typescript type
Expand All @@ -26,6 +26,10 @@ export const pbSchemaTypescriptMap = {
// Dependent on schema
file: (fieldSchema: FieldSchema) =>
fieldSchema.maxSelect && fieldSchema.maxSelect > 1 ? "string[]" : "string",
file_create: (fieldSchema: FieldSchema) =>
fieldSchema.maxSelect && fieldSchema.maxSelect > 1 ? "File[]" : "File",
file_update: (fieldSchema: FieldSchema) =>
fieldSchema.maxSelect && fieldSchema.maxSelect > 1 ? "Nullable<File[]>" : "Nullable<File>",
json: (fieldSchema: FieldSchema) =>
`null | ${fieldNameToGeneric(fieldSchema.name)}`,
relation: (fieldSchema: FieldSchema) =>
Expand Down Expand Up @@ -78,6 +82,65 @@ export function createTypeField(
return `\t${fieldName}${required}: ${typeString}`
}

export function createTypeCreateField(
collectionName: string,
fieldSchema: FieldSchema,
): string {
let typeStringOrFunc:
| string
| ((fieldSchema: FieldSchema, collectionName: string) => string)

const fieldType = fieldSchema.type === 'file' ? 'file_create' : fieldSchema.type;
if (!(fieldType in pbSchemaTypescriptMap)) {
console.log(`WARNING: unknown type "${fieldType}" found in schema`)
typeStringOrFunc = "unknown"
} else {
typeStringOrFunc =
pbSchemaTypescriptMap[
fieldType as keyof typeof pbSchemaTypescriptMap
]
}

const typeString =
typeof typeStringOrFunc === "function"
? typeStringOrFunc(fieldSchema, collectionName)
: typeStringOrFunc

const fieldName = sanitizeFieldName(fieldSchema.name)
const required = fieldSchema.required ? "" : "?"

return `\t${fieldName}${required}: ${typeString}`
}

export function createTypeUpdateField(
collectionName: string,
fieldSchema: FieldSchema,
): string {
let typeStringOrFunc:
| string
| ((fieldSchema: FieldSchema, collectionName: string) => string)

const fieldType = fieldSchema.type === 'file' ? 'file_update' : fieldSchema.type;
if (!(fieldType in pbSchemaTypescriptMap)) {
console.log(`WARNING: unknown type "${fieldType}" found in schema`)
typeStringOrFunc = "unknown"
} else {
typeStringOrFunc =
pbSchemaTypescriptMap[
fieldType as keyof typeof pbSchemaTypescriptMap
]
}

const typeString =
typeof typeStringOrFunc === "function"
? typeStringOrFunc(fieldSchema, collectionName)
: typeStringOrFunc

const fieldName = sanitizeFieldName(fieldSchema.name)

return `\t${fieldName}?: ${typeString}`
}

export function createSelectOptions(
recordName: string,
fields: Array<FieldSchema>
Expand Down
Loading
Loading