-
-
Save RepComm/c1a2f1d8d8dc52d954eb01ab88866153 to your computer and use it in GitHub Desktop.
| #!/usr/bin/env node | |
| import { readFileSync } from "fs"; | |
| import { exit } from "process"; | |
| function log(...msgs) { | |
| console.log("//", ...msgs); | |
| } | |
| function warn(...msgs) { | |
| log("WARN:", ...msgs); | |
| } | |
| log( | |
| "schema2jsdoc - https://gist.github.com/RepComm/c1a2f1d8d8dc52d954eb01ab88866153", | |
| ); | |
| function error(...msgs) { | |
| console.error("//ERROR: ", ...msgs); | |
| } | |
| let schemaData = ""; | |
| try { | |
| //read stdin (aka 0 in the first arg) fully to text instead of an actual file | |
| //https://stackoverflow.com/a/56012724/8112809 | |
| schemaData = readFileSync(0, { encoding: "utf-8" }); | |
| } catch (ex) { | |
| error( | |
| "couldn't read schema from standard input, try: cat pb_schema.json | ./schema2jsdoc.mjs", | |
| ex, | |
| ); | |
| exit(2); | |
| } | |
| /**@typedef {import("./schema2jsdoc").pb_schema} pb_schema*/ | |
| /**@type {pb_schema}*/ | |
| let schemaJson = {}; | |
| try { | |
| schemaJson = JSON.parse(schemaData); | |
| } catch (ex) { | |
| error( | |
| "couldn't parse schema json, try: cat pb_schema.json | ./schema2jsdoc.mjs", | |
| ex, | |
| ); | |
| exit(3); | |
| } | |
| if (!Array.isArray(schemaJson)) { | |
| error("schema root is not an array"); | |
| exit(4); | |
| } | |
| if (schemaJson.length < 1) { | |
| error("schema root array length < 1"); | |
| exit(5); | |
| } | |
| function fieldToPropType(field_type, relationIsMultiple = false) { | |
| switch (field_type) { | |
| case "text": | |
| return "string"; | |
| case "number": | |
| return "number"; | |
| case "relation": | |
| if (relationIsMultiple) { | |
| return "string[]"; | |
| } else { | |
| return "string"; | |
| } | |
| case "editor": | |
| return "string"; | |
| case "file": | |
| return "string"; | |
| case "select": | |
| return "string"; | |
| case "bool": | |
| return "boolean"; | |
| default: | |
| warn(`unknown field type '${field_type}' using 'any' as a catch-all`); | |
| return "any"; | |
| } | |
| } | |
| /** May be incomplete, but should handle many cases | |
| * Used as a reference | |
| * https://www.w3schools.com/js/js_reserved.asp | |
| */ | |
| const JS_RESERVED_WORDS = new Set([ | |
| "arguments", | |
| "await", | |
| "break", | |
| "case", | |
| "catch", | |
| "class", | |
| "const", | |
| "continue", | |
| "debugger", | |
| "default", | |
| "delete", | |
| "do", | |
| "else", | |
| "enum", | |
| "eval", | |
| "export", | |
| "extends", | |
| "false", | |
| "finally", | |
| "for", | |
| "function", | |
| "if", | |
| "implements", | |
| "import", | |
| "in", | |
| "instanceof", | |
| "interface", | |
| "let", | |
| "native", | |
| "new", | |
| "null", | |
| "package", | |
| "private", | |
| "protected", | |
| "public", | |
| "return", | |
| "static", | |
| "super", | |
| "switch", | |
| "this", | |
| "throw", | |
| "throws", | |
| "true", | |
| "try", | |
| "typeof", | |
| "var", | |
| "void", | |
| "while", | |
| "with", | |
| "yield", | |
| ]); | |
| function resolveName(field_name) { | |
| if (JS_RESERVED_WORDS.has(field_name)) { | |
| return `__${field_name}`; | |
| } else { | |
| return field_name; | |
| } | |
| } | |
| let output = ""; | |
| output += | |
| 'declare import PocketBaseImport, {RecordService,RecordModel} from "pocketbase";\n'; | |
| const collectionToInterfaceNameMap = new Map(); | |
| const collectionNameToIdMap = new Map(); | |
| for (const entry of schemaJson) { | |
| //not all strings are valid typescript interface names, quell some common issues here | |
| const ifname = resolveName(entry.name); | |
| //track collection id - used for relation mapping of 'expand' property in typescript definition output | |
| collectionNameToIdMap.set(ifname, entry.id); | |
| //save the remapping for later for output type pb_schema_map | |
| collectionToInterfaceNameMap.set(entry.name, ifname); | |
| //begin writing the interface | |
| output += `interface ${ifname} extends RecordModel {\n`; | |
| const fieldNameToRelationIdMap = new Map(); | |
| const fieldNameToRelationExpandIsArray = new Map(); | |
| //output props of collection types | |
| for (const field of entry.fields) { | |
| if (field.type === "relation") { | |
| fieldNameToRelationIdMap.set(field.name, field.collectionId); | |
| output += ` /**relation id, use .expand property*/\n`; | |
| } | |
| let relationIsMultiple = false; | |
| if (!field.maxSelect || field.maxSelect !== 1) { | |
| relationIsMultiple = true; | |
| //output += "//DEBUG: maxSelect: " + field.maxSelect + "\n"; | |
| fieldNameToRelationExpandIsArray.set(field.name, true); | |
| } | |
| const ft = fieldToPropType(field.type, relationIsMultiple); | |
| output += ` ${field.name}: ${ft};\n`; | |
| } | |
| //output expand prop if necessary | |
| if (fieldNameToRelationIdMap.size > 1) { | |
| output += ` expand?: {\n`; | |
| for (const [name, collectionId] of fieldNameToRelationIdMap) { | |
| //use the relation collection id to avoid extra looping thru schema for lookups | |
| //typescript definitions are good at this anyways, plus it looks cool | |
| if (fieldNameToRelationExpandIsArray.get(name) === true) { | |
| output += ` ${name}: CollectionIdNameMap["${collectionId}"][];\n`; | |
| } else { | |
| output += ` ${name}: CollectionIdNameMap["${collectionId}"];\n`; | |
| } | |
| } | |
| output += " }\n"; | |
| } | |
| //end the interface | |
| output += "}\n"; | |
| } | |
| //output pb_schema_map for mapping collection names to interface names | |
| output += "export interface pb_schema_map {\n"; | |
| for (const [k, v] of collectionToInterfaceNameMap) { | |
| output += ` "${k}": ${v};\n`; | |
| } | |
| output += "}\n"; | |
| //output TypedPocketBase | |
| output += "export interface TypedPocketBase extends PocketBaseImport {\n"; | |
| output += " collection(idOrName: string): RecordService;\n"; | |
| for (const [k, v] of collectionToInterfaceNameMap) { | |
| output += ` collection(idOrName: "${k}"): RecordService<${v}>;\n`; | |
| } | |
| output += "}\n"; | |
| //output CollectionIdNameMap for mapping collection ids to interfaces | |
| output += "interface CollectionIdNameMap {\n"; | |
| for (const [name, id] of collectionNameToIdMap) { | |
| output += ` "${id}": ${name};\n`; | |
| } | |
| output += "}\n"; | |
| //output result | |
| console.log(output); |
TODO - map "expand" props for relation field types
revision 4
map expands complete! and it is sick.
no longer tries to read pb_schema.json from current directory, instead expects to be passed pb_schema.json contents passed to standard input, example:
cat ./pb_schema.json | ./schema2jsdoc.mjs > schema.d.tsthis above line uses 'cat' command to echo content from a file 'pb_schema.json', pipes the output to schema2jsdoc.mjs shell script from this gist, and diverts output from that to schema.d.ts file where type defs will be written
example output as of now
// schema2jsdoc - https://gist.github.com/RepComm/c1a2f1d8d8dc52d954eb01ab88866153
interface players {
name: string;
avatar: string;
}
interface alignments {
name: string;
}
interface appearances {
image: string;
name: string;
characters: string;
}
interface armor_classes {
name: string;
}
interface background {
name: string;
}
interface characters {
name: string;
player: string;
class: string;
level: number;
background: string;
race: string;
alignment: string;
xp: number;
strength: number;
dexterity: number;
constitution: number;
intelligence: number;
wisdom: number;
charisma: number;
wisdom_passive: number;
armor_class: string;
speed: number;
personality_traits: string;
ideals: string;
bonds: string;
flaws: string;
age: number;
height: number;
weight: number;
eyes: string;
skin: string;
hair: string;
appearances: string;
backstory: string;
treasure: string;
additional_features_and_traits: string;
expand?: {
player: CollectionIdNameMap["_pb_users_auth_"];
class: CollectionIdNameMap["6p9jdpn1ashwh1k"];
background: CollectionIdNameMap["v5d83ccwckijday"];
race: CollectionIdNameMap["irasc0g38drvkxy"];
alignment: CollectionIdNameMap["kugurr24ahjs4nm"];
armor_class: CollectionIdNameMap["r6u975oqxkxv66h"];
appearances: CollectionIdNameMap["y99prlxo6k4folv"];
treasure: CollectionIdNameMap["7bnpj5je1dn6jfi"];
}
}
interface __class {
name: string;
}
interface races {
name: string;
}
interface treasures {
name: string;
image: string;
}
export interface pb_schema_map {
"players": players;
"alignments": alignments;
"appearances": appearances;
"armor_classes": armor_classes;
"background": background;
"characters": characters;
"class": __class;
"races": races;
"treasures": treasures;
}
interface CollectionIdNameMap {
"_pb_users_auth_": players;
"kugurr24ahjs4nm": alignments;
"y99prlxo6k4folv": appearances;
"r6u975oqxkxv66h": armor_classes;
"v5d83ccwckijday": background;
"knyh02k466va30x": characters;
"6p9jdpn1ashwh1k": __class;
"irasc0g38drvkxy": races;
"7bnpj5je1dn6jfi": treasures;
}revision 5 - output comment about relation fields for each relation field in type definitions output for more convenience while using the types output by the tool
Revision 6 - fixed bug where 'warn' and 'panic' functions didn't exist, replaced 'panic' with nodejs exit, and 'warn' calls 'log' now
Revision 8 - exports "TypedPocketBase", an interface that extends "Pocketbase" imported from "pocketbase", which you can use like so:
import type { TypedPocketBase } from "./schema";
const pb: TypedPocketBase = new Pocketbase();as mentioned in pocketbase docs: https://github.com/pocketbase/js-sdk?tab=readme-ov-file#specify-typescript-definitions
Revision 9 - collection types extend RecordModel from PocketBaseImport now, so properties of records will have built-in property type definitions like record.id
Revision 10 - multiple relation now maps to expands field as array instead of singular, which was incorrect.
Revision 11 - updated to handle PocketBase v0.23.8 pb_schema.json
Revision 12 - updated to handle relation maxSelect!=1 field type as string[] instead of string
revision 3 - remaps most reserved js keywords in interface names to __${name} to prevent issues such as naming a pocketbase table "class" (shame on you, you should be using plural for collection names anyways! ;-) )
also outputs exported interface pb_schema_map with all the fields mapped with original names as string keys, and the interfaces as values