@rebasepro/sdk-generator 0.0.1-canary.4d4fb3e
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +6 -0
- package/dist/common/src/collections/CollectionRegistry.d.ts +48 -0
- package/dist/common/src/collections/index.d.ts +1 -0
- package/dist/common/src/data/buildRebaseData.d.ts +14 -0
- package/dist/common/src/index.d.ts +3 -0
- package/dist/common/src/util/builders.d.ts +57 -0
- package/dist/common/src/util/callbacks.d.ts +6 -0
- package/dist/common/src/util/collections.d.ts +11 -0
- package/dist/common/src/util/common.d.ts +2 -0
- package/dist/common/src/util/conditions.d.ts +26 -0
- package/dist/common/src/util/entities.d.ts +36 -0
- package/dist/common/src/util/enums.d.ts +3 -0
- package/dist/common/src/util/index.d.ts +16 -0
- package/dist/common/src/util/navigation_from_path.d.ts +34 -0
- package/dist/common/src/util/navigation_utils.d.ts +20 -0
- package/dist/common/src/util/parent_references_from_path.d.ts +6 -0
- package/dist/common/src/util/paths.d.ts +14 -0
- package/dist/common/src/util/permissions.d.ts +5 -0
- package/dist/common/src/util/references.d.ts +2 -0
- package/dist/common/src/util/relations.d.ts +12 -0
- package/dist/common/src/util/resolutions.d.ts +72 -0
- package/dist/common/src/util/storage.d.ts +24 -0
- package/dist/index.cjs +211 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.es.js +208 -0
- package/dist/index.es.js.map +1 -0
- package/dist/sdk-generator/src/generate-types.d.ts +2 -0
- package/dist/sdk-generator/src/index.d.ts +19 -0
- package/dist/sdk-generator/src/utils.d.ts +22 -0
- package/dist/types/src/controllers/analytics_controller.d.ts +7 -0
- package/dist/types/src/controllers/auth.d.ts +117 -0
- package/dist/types/src/controllers/client.d.ts +58 -0
- package/dist/types/src/controllers/collection_registry.d.ts +44 -0
- package/dist/types/src/controllers/customization_controller.d.ts +54 -0
- package/dist/types/src/controllers/data.d.ts +141 -0
- package/dist/types/src/controllers/data_driver.d.ts +168 -0
- package/dist/types/src/controllers/database_admin.d.ts +11 -0
- package/dist/types/src/controllers/dialogs_controller.d.ts +36 -0
- package/dist/types/src/controllers/effective_role.d.ts +4 -0
- package/dist/types/src/controllers/index.d.ts +17 -0
- package/dist/types/src/controllers/local_config_persistence.d.ts +20 -0
- package/dist/types/src/controllers/navigation.d.ts +213 -0
- package/dist/types/src/controllers/registry.d.ts +51 -0
- package/dist/types/src/controllers/side_dialogs_controller.d.ts +67 -0
- package/dist/types/src/controllers/side_entity_controller.d.ts +89 -0
- package/dist/types/src/controllers/snackbar.d.ts +24 -0
- package/dist/types/src/controllers/storage.d.ts +173 -0
- package/dist/types/src/index.d.ts +4 -0
- package/dist/types/src/rebase_context.d.ts +101 -0
- package/dist/types/src/types/backend.d.ts +533 -0
- package/dist/types/src/types/builders.d.ts +14 -0
- package/dist/types/src/types/chips.d.ts +5 -0
- package/dist/types/src/types/collections.d.ts +812 -0
- package/dist/types/src/types/data_source.d.ts +64 -0
- package/dist/types/src/types/entities.d.ts +145 -0
- package/dist/types/src/types/entity_actions.d.ts +98 -0
- package/dist/types/src/types/entity_callbacks.d.ts +173 -0
- package/dist/types/src/types/entity_link_builder.d.ts +7 -0
- package/dist/types/src/types/entity_overrides.d.ts +9 -0
- package/dist/types/src/types/entity_views.d.ts +61 -0
- package/dist/types/src/types/export_import.d.ts +21 -0
- package/dist/types/src/types/index.d.ts +22 -0
- package/dist/types/src/types/locales.d.ts +4 -0
- package/dist/types/src/types/modify_collections.d.ts +5 -0
- package/dist/types/src/types/plugins.d.ts +225 -0
- package/dist/types/src/types/properties.d.ts +1091 -0
- package/dist/types/src/types/property_config.d.ts +70 -0
- package/dist/types/src/types/relations.d.ts +336 -0
- package/dist/types/src/types/slots.d.ts +228 -0
- package/dist/types/src/types/translations.d.ts +826 -0
- package/dist/types/src/types/user_management_delegate.d.ts +120 -0
- package/dist/types/src/types/websockets.d.ts +78 -0
- package/dist/types/src/users/index.d.ts +2 -0
- package/dist/types/src/users/roles.d.ts +22 -0
- package/dist/types/src/users/user.d.ts +46 -0
- package/jest.config.cjs +13 -0
- package/package.json +51 -0
- package/src/generate-types.ts +176 -0
- package/src/index.ts +71 -0
- package/src/json-logic-js.d.ts +8 -0
- package/src/utils.ts +42 -0
- package/test/sdk-generator.test.ts +78 -0
- package/tsconfig.json +26 -0
- package/vite.config.ts +49 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
(function(global, factory) {
|
|
2
|
+
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("@rebasepro/common")) : typeof define === "function" && define.amd ? define(["exports", "@rebasepro/common"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.RebaseSDKGenerator = {}, global.common));
|
|
3
|
+
})(this, (function(exports2, common) {
|
|
4
|
+
"use strict";
|
|
5
|
+
function toPascalCase(str) {
|
|
6
|
+
return str.split(/[_\-\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
|
|
7
|
+
}
|
|
8
|
+
function toCamelCase(str) {
|
|
9
|
+
const pascal = toPascalCase(str);
|
|
10
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
11
|
+
}
|
|
12
|
+
function toSafeIdentifier(str) {
|
|
13
|
+
return toCamelCase(str.replace(/[^a-zA-Z0-9_]/g, "_"));
|
|
14
|
+
}
|
|
15
|
+
function indent(text, spaces) {
|
|
16
|
+
const pad = " ".repeat(spaces);
|
|
17
|
+
return text.split("\n").map((line) => line.trim() ? pad + line : line).join("\n");
|
|
18
|
+
}
|
|
19
|
+
function propertyToTypeScriptType(prop) {
|
|
20
|
+
switch (prop.type) {
|
|
21
|
+
case "string": {
|
|
22
|
+
const sp = prop;
|
|
23
|
+
if (sp.enum) {
|
|
24
|
+
const ids = Array.isArray(sp.enum) ? sp.enum.map((e) => typeof e === "object" ? String(e.id) : String(e)) : Object.keys(sp.enum);
|
|
25
|
+
return ids.map((v) => `"${v}"`).join(" | ");
|
|
26
|
+
}
|
|
27
|
+
return "string";
|
|
28
|
+
}
|
|
29
|
+
case "number": {
|
|
30
|
+
const np = prop;
|
|
31
|
+
if (np.enum) {
|
|
32
|
+
const ids = Array.isArray(np.enum) ? np.enum.map((e) => typeof e === "object" ? String(e.id) : String(e)) : Object.keys(np.enum);
|
|
33
|
+
return ids.join(" | ");
|
|
34
|
+
}
|
|
35
|
+
return "number";
|
|
36
|
+
}
|
|
37
|
+
case "boolean":
|
|
38
|
+
return "boolean";
|
|
39
|
+
case "date":
|
|
40
|
+
return "string";
|
|
41
|
+
// ISO 8601 string over the wire
|
|
42
|
+
case "geopoint":
|
|
43
|
+
return "{ latitude: number; longitude: number; }";
|
|
44
|
+
case "reference":
|
|
45
|
+
return "string | number";
|
|
46
|
+
case "relation":
|
|
47
|
+
return "string | number";
|
|
48
|
+
case "map": {
|
|
49
|
+
const mapProp = prop;
|
|
50
|
+
if (mapProp.properties) {
|
|
51
|
+
const inner = Object.entries(mapProp.properties).map(([k, v]) => `${toSafeIdentifier(k)}: ${propertyToTypeScriptType(v)};`).join(" ");
|
|
52
|
+
return `{ ${inner} }`;
|
|
53
|
+
}
|
|
54
|
+
return "Record<string, any>";
|
|
55
|
+
}
|
|
56
|
+
case "array": {
|
|
57
|
+
const arrProp = prop;
|
|
58
|
+
if (arrProp.of) {
|
|
59
|
+
return `Array<${propertyToTypeScriptType(arrProp.of)}>`;
|
|
60
|
+
}
|
|
61
|
+
return "Array<any>";
|
|
62
|
+
}
|
|
63
|
+
default:
|
|
64
|
+
return "any";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function generateTypedefs(collections) {
|
|
68
|
+
const lines = [
|
|
69
|
+
`/**`,
|
|
70
|
+
` * This file was auto-generated by Rebase.`,
|
|
71
|
+
` * Do not make direct changes to the file.`,
|
|
72
|
+
` */`,
|
|
73
|
+
``,
|
|
74
|
+
`export interface Database {`
|
|
75
|
+
];
|
|
76
|
+
for (const collection of collections) {
|
|
77
|
+
toPascalCase(collection.slug);
|
|
78
|
+
const properties = collection.properties ?? {};
|
|
79
|
+
let resolvedRelations = {};
|
|
80
|
+
try {
|
|
81
|
+
resolvedRelations = common.resolveCollectionRelations(collection);
|
|
82
|
+
} catch {
|
|
83
|
+
}
|
|
84
|
+
lines.push(` ${toSafeIdentifier(collection.slug)}: {`);
|
|
85
|
+
lines.push(` Row: {`);
|
|
86
|
+
const emittedKeys = /* @__PURE__ */ new Set();
|
|
87
|
+
for (const [key, rawProp] of Object.entries(properties)) {
|
|
88
|
+
const prop = rawProp;
|
|
89
|
+
if (prop.type === "relation") continue;
|
|
90
|
+
const tsType = propertyToTypeScriptType(prop);
|
|
91
|
+
const isRequired = prop.validation?.required;
|
|
92
|
+
lines.push(` ${toSafeIdentifier(key)}${isRequired ? "" : "?"}: ${tsType};`);
|
|
93
|
+
emittedKeys.add(key);
|
|
94
|
+
}
|
|
95
|
+
for (const [relKey, relation] of Object.entries(resolvedRelations)) {
|
|
96
|
+
if (relation.direction === "owning" && relation.cardinality === "one" && relation.localKey) {
|
|
97
|
+
const fkKey = relation.localKey;
|
|
98
|
+
if (emittedKeys.has(fkKey)) continue;
|
|
99
|
+
let fkType = "string | number";
|
|
100
|
+
try {
|
|
101
|
+
const target = relation.target();
|
|
102
|
+
if (target.properties) {
|
|
103
|
+
const idProp = Object.entries(target.properties).find(([_, p]) => p.isId);
|
|
104
|
+
if (idProp) {
|
|
105
|
+
fkType = idProp[1].type === "number" ? "number" : "string";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
const isRequired = relation.validation?.required;
|
|
111
|
+
lines.push(` ${toSafeIdentifier(fkKey)}${isRequired ? "" : "?"}: ${fkType};`);
|
|
112
|
+
emittedKeys.add(fkKey);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
lines.push(` };`);
|
|
116
|
+
lines.push(` Insert: {`);
|
|
117
|
+
emittedKeys.clear();
|
|
118
|
+
for (const [key, rawProp] of Object.entries(properties)) {
|
|
119
|
+
const prop = rawProp;
|
|
120
|
+
if (prop.type === "relation") continue;
|
|
121
|
+
const tsType = propertyToTypeScriptType(prop);
|
|
122
|
+
const isRequired = prop.validation?.required;
|
|
123
|
+
const typedProp = prop;
|
|
124
|
+
const isAutoId = "isId" in prop && typedProp.isId && typedProp.isId !== "manual" && typedProp.isId !== true;
|
|
125
|
+
const isOptional = !isRequired || isAutoId;
|
|
126
|
+
lines.push(` ${toSafeIdentifier(key)}${isOptional ? "?" : ""}: ${tsType};`);
|
|
127
|
+
emittedKeys.add(key);
|
|
128
|
+
}
|
|
129
|
+
for (const [relKey, relation] of Object.entries(resolvedRelations)) {
|
|
130
|
+
if (relation.direction === "owning" && relation.cardinality === "one" && relation.localKey) {
|
|
131
|
+
const fkKey = relation.localKey;
|
|
132
|
+
if (emittedKeys.has(fkKey)) continue;
|
|
133
|
+
let fkType = "string | number";
|
|
134
|
+
const isRequired = relation.validation?.required;
|
|
135
|
+
lines.push(` ${toSafeIdentifier(fkKey)}${isRequired ? "" : "?"}: ${fkType};`);
|
|
136
|
+
emittedKeys.add(fkKey);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
lines.push(` };`);
|
|
140
|
+
lines.push(` Update: {`);
|
|
141
|
+
emittedKeys.clear();
|
|
142
|
+
for (const [key, rawProp] of Object.entries(properties)) {
|
|
143
|
+
const prop = rawProp;
|
|
144
|
+
if (prop.type === "relation") continue;
|
|
145
|
+
const tsType = propertyToTypeScriptType(prop);
|
|
146
|
+
lines.push(` ${toSafeIdentifier(key)}?: ${tsType};`);
|
|
147
|
+
emittedKeys.add(key);
|
|
148
|
+
}
|
|
149
|
+
for (const [relKey, relation] of Object.entries(resolvedRelations)) {
|
|
150
|
+
if (relation.direction === "owning" && relation.cardinality === "one" && relation.localKey) {
|
|
151
|
+
const fkKey = relation.localKey;
|
|
152
|
+
if (emittedKeys.has(fkKey)) continue;
|
|
153
|
+
lines.push(` ${toSafeIdentifier(fkKey)}?: string | number;`);
|
|
154
|
+
emittedKeys.add(fkKey);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
lines.push(` };`);
|
|
158
|
+
lines.push(` };`);
|
|
159
|
+
}
|
|
160
|
+
lines.push(`}`);
|
|
161
|
+
lines.push(``);
|
|
162
|
+
return lines.join("\n");
|
|
163
|
+
}
|
|
164
|
+
function generateSDK(collections, options = {}) {
|
|
165
|
+
const files = [];
|
|
166
|
+
files.push({
|
|
167
|
+
path: "database.types.ts",
|
|
168
|
+
content: generateTypedefs(collections)
|
|
169
|
+
});
|
|
170
|
+
if (options.includeReadme !== false) {
|
|
171
|
+
files.push({
|
|
172
|
+
path: "README.md",
|
|
173
|
+
content: `# Rebase SDK
|
|
174
|
+
|
|
175
|
+
> Auto-generated by \`rebase generate-sdk\`. Do not edit manually.
|
|
176
|
+
|
|
177
|
+
## Usage
|
|
178
|
+
|
|
179
|
+
1. Install the client package:
|
|
180
|
+
\`\`\`bash
|
|
181
|
+
npm install @rebasepro/client
|
|
182
|
+
\`\`\`
|
|
183
|
+
|
|
184
|
+
2. Initialize with your generated types:
|
|
185
|
+
\`\`\`typescript
|
|
186
|
+
import { createRebaseClient } from '@rebasepro/client';
|
|
187
|
+
import type { Database } from './database.types';
|
|
188
|
+
|
|
189
|
+
const rebase = createRebaseClient<Database>({
|
|
190
|
+
baseUrl: 'http://localhost:3001',
|
|
191
|
+
// token: '...', // User token if not using auth module
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Both syntax styles are fully typed!
|
|
195
|
+
const { data: users } = await rebase.data.users.find();
|
|
196
|
+
const { data: posts } = await rebase.data.collection('posts').find();
|
|
197
|
+
\`\`\`
|
|
198
|
+
`
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return files;
|
|
202
|
+
}
|
|
203
|
+
exports2.generateSDK = generateSDK;
|
|
204
|
+
exports2.generateTypedefs = generateTypedefs;
|
|
205
|
+
exports2.indent = indent;
|
|
206
|
+
exports2.toCamelCase = toCamelCase;
|
|
207
|
+
exports2.toPascalCase = toPascalCase;
|
|
208
|
+
exports2.toSafeIdentifier = toSafeIdentifier;
|
|
209
|
+
Object.defineProperty(exports2, Symbol.toStringTag, { value: "Module" });
|
|
210
|
+
}));
|
|
211
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/utils.ts","../src/generate-types.ts","../src/index.ts"],"sourcesContent":["/**\n * Utility functions for the SDK generator\n */\n\n/**\n * Convert a slug/snake_case string to PascalCase\n * e.g. \"private_notes\" → \"PrivateNotes\"\n */\nexport function toPascalCase(str: string): string {\n return str\n .split(/[_\\-\\s]+/)\n .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(\"\");\n}\n\n/**\n * Convert a slug/snake_case string to camelCase\n * e.g. \"private_notes\" → \"privateNotes\"\n */\nexport function toCamelCase(str: string): string {\n const pascal = toPascalCase(str);\n return pascal.charAt(0).toLowerCase() + pascal.slice(1);\n}\n\n/**\n * Convert a slug to a safe JS identifier\n * e.g. \"private-notes\" → \"privateNotes\"\n */\nexport function toSafeIdentifier(str: string): string {\n return toCamelCase(str.replace(/[^a-zA-Z0-9_]/g, \"_\"));\n}\n\n/**\n * Indent a block of text by a given number of spaces\n */\nexport function indent(text: string, spaces: number): string {\n const pad = \" \".repeat(spaces);\n return text\n .split(\"\\n\")\n .map(line => (line.trim() ? pad + line : line))\n .join(\"\\n\");\n}\n","import { EntityCollection, PostgresCollection, Property, Properties, MapProperty, ArrayProperty, RelationProperty, StringProperty, NumberProperty } from \"@rebasepro/types\";\nimport { resolveCollectionRelations } from \"@rebasepro/common\";\nimport { toPascalCase, toSafeIdentifier } from \"./utils\";\n\nfunction propertyToTypeScriptType(prop: Property): string {\n switch (prop.type) {\n case \"string\": {\n const sp = prop as StringProperty;\n if (sp.enum) {\n const ids = Array.isArray(sp.enum)\n ? sp.enum.map((e: any) => typeof e === \"object\" ? String(e.id) : String(e))\n : Object.keys(sp.enum);\n return ids.map(v => `\"${v}\"`).join(\" | \");\n }\n return \"string\";\n }\n case \"number\": {\n const np = prop as NumberProperty;\n if (np.enum) {\n const ids = Array.isArray(np.enum)\n ? np.enum.map((e: any) => typeof e === \"object\" ? String(e.id) : String(e))\n : Object.keys(np.enum);\n return ids.join(\" | \");\n }\n return \"number\";\n }\n case \"boolean\":\n return \"boolean\";\n case \"date\":\n return \"string\"; // ISO 8601 string over the wire\n case \"geopoint\":\n return \"{ latitude: number; longitude: number; }\";\n case \"reference\":\n return \"string | number\";\n case \"relation\":\n return \"string | number\";\n case \"map\": {\n const mapProp = prop as MapProperty;\n if (mapProp.properties) {\n const inner = Object.entries(mapProp.properties)\n .map(([k, v]) => `${toSafeIdentifier(k)}: ${propertyToTypeScriptType(v as Property)};`)\n .join(\" \");\n return `{ ${inner} }`;\n }\n return \"Record<string, any>\";\n }\n case \"array\": {\n const arrProp = prop as ArrayProperty;\n if (arrProp.of) {\n return `Array<${propertyToTypeScriptType(arrProp.of as Property)}>`;\n }\n return \"Array<any>\";\n }\n default:\n return \"any\";\n }\n}\n\nexport function generateTypedefs(collections: EntityCollection[]): string {\n const lines: string[] = [\n `/**`,\n ` * This file was auto-generated by Rebase.`,\n ` * Do not make direct changes to the file.`,\n ` */`,\n ``,\n `export interface Database {`\n ];\n\n for (const collection of collections) {\n const typeName = toPascalCase(collection.slug);\n const properties = (collection.properties ?? {}) as Properties;\n\n // Resolve relations\n let resolvedRelations: Record<string, any> = {};\n try {\n resolvedRelations = resolveCollectionRelations(collection as PostgresCollection);\n } catch { /* ignore */ }\n\n lines.push(` ${toSafeIdentifier(collection.slug)}: {`);\n \n // ── Row Type ──\n lines.push(` Row: {`);\n const emittedKeys = new Set<string>();\n\n // 1. Direct properties\n for (const [key, rawProp] of Object.entries(properties)) {\n const prop = rawProp as Property;\n if (prop.type === \"relation\") continue; \n\n const tsType = propertyToTypeScriptType(prop);\n const isRequired = prop.validation?.required;\n lines.push(` ${toSafeIdentifier(key)}${isRequired ? \"\" : \"?\"}: ${tsType};`);\n emittedKeys.add(key);\n }\n \n // 2. FK columns from relations\n for (const [relKey, relation] of Object.entries(resolvedRelations)) {\n if (relation.direction === \"owning\" && relation.cardinality === \"one\" && relation.localKey) {\n const fkKey = relation.localKey;\n if (emittedKeys.has(fkKey)) continue;\n\n let fkType = \"string | number\";\n try {\n const target = relation.target();\n if (target.properties) {\n const idProp = Object.entries(target.properties).find(([_, p]: [string, any]) => p.isId);\n if (idProp) {\n fkType = (idProp[1] as Property).type === \"number\" ? \"number\" : \"string\";\n }\n }\n } catch { /* ignore */ }\n\n const isRequired = relation.validation?.required;\n lines.push(` ${toSafeIdentifier(fkKey)}${isRequired ? \"\" : \"?\"}: ${fkType};`);\n emittedKeys.add(fkKey);\n }\n }\n lines.push(` };`);\n\n // ── Insert Type ──\n lines.push(` Insert: {`);\n emittedKeys.clear();\n\n for (const [key, rawProp] of Object.entries(properties)) {\n const prop = rawProp as Property;\n if (prop.type === \"relation\") continue; \n const tsType = propertyToTypeScriptType(prop);\n const isRequired = prop.validation?.required;\n const typedProp = prop as StringProperty | NumberProperty;\n const isAutoId = \"isId\" in prop && typedProp.isId && typedProp.isId !== \"manual\" && typedProp.isId !== true;\n const isOptional = !isRequired || isAutoId;\n lines.push(` ${toSafeIdentifier(key)}${isOptional ? \"?\" : \"\"}: ${tsType};`);\n emittedKeys.add(key);\n }\n\n for (const [relKey, relation] of Object.entries(resolvedRelations)) {\n if (relation.direction === \"owning\" && relation.cardinality === \"one\" && relation.localKey) {\n const fkKey = relation.localKey;\n if (emittedKeys.has(fkKey)) continue;\n let fkType = \"string | number\";\n // simple fallback\n const isRequired = relation.validation?.required;\n lines.push(` ${toSafeIdentifier(fkKey)}${isRequired ? \"\" : \"?\"}: ${fkType};`);\n emittedKeys.add(fkKey);\n }\n }\n lines.push(` };`);\n\n // ── Update Type ──\n lines.push(` Update: {`);\n emittedKeys.clear();\n for (const [key, rawProp] of Object.entries(properties)) {\n const prop = rawProp as Property;\n if (prop.type === \"relation\") continue; \n const tsType = propertyToTypeScriptType(prop);\n lines.push(` ${toSafeIdentifier(key)}?: ${tsType};`);\n emittedKeys.add(key);\n }\n for (const [relKey, relation] of Object.entries(resolvedRelations)) {\n if (relation.direction === \"owning\" && relation.cardinality === \"one\" && relation.localKey) {\n const fkKey = relation.localKey;\n if (emittedKeys.has(fkKey)) continue;\n lines.push(` ${toSafeIdentifier(fkKey)}?: string | number;`);\n emittedKeys.add(fkKey);\n }\n }\n lines.push(` };`);\n\n lines.push(` };`);\n }\n\n lines.push(`}`);\n lines.push(``);\n\n return lines.join(\"\\n\");\n}\n","/**\n * @rebasepro/sdk-generator\n *\n * Generates a purely typed Typescript database definition.\n */\n\nimport { EntityCollection } from \"@rebasepro/types\";\nimport { generateTypedefs } from \"./generate-types\";\n\nexport { generateTypedefs } from \"./generate-types\";\nexport { toPascalCase, toCamelCase, toSafeIdentifier, indent } from \"./utils\";\n\n// ─── Public API ────────────────────────────────────────────────────\n\nexport interface GeneratedFile {\n /** Relative file path within the output directory */\n path: string;\n /** File content */\n content: string;\n}\n\nexport interface GenerateSDKOptions {\n /** Whether to include a README file (default: true) */\n includeReadme?: boolean;\n}\n\nexport function generateSDK(\n collections: EntityCollection[],\n options: GenerateSDKOptions = {},\n): GeneratedFile[] {\n const files: GeneratedFile[] = [];\n\n files.push({\n path: \"database.types.ts\",\n content: generateTypedefs(collections)\n });\n\n if (options.includeReadme !== false) {\n files.push({\n path: \"README.md\",\n content: `# Rebase SDK\n\n> Auto-generated by \\`rebase generate-sdk\\`. Do not edit manually.\n\n## Usage\n\n1. Install the client package:\n \\`\\`\\`bash\n npm install @rebasepro/client\n \\`\\`\\`\n\n2. Initialize with your generated types:\n \\`\\`\\`typescript\n import { createRebaseClient } from '@rebasepro/client';\n import type { Database } from './database.types';\n\n const rebase = createRebaseClient<Database>({\n baseUrl: 'http://localhost:3001',\n // token: '...', // User token if not using auth module\n });\n\n // Both syntax styles are fully typed!\n const { data: users } = await rebase.data.users.find();\n const { data: posts } = await rebase.data.collection('posts').find();\n \\`\\`\\`\n`\n });\n }\n\n return files;\n}\n"],"names":["resolveCollectionRelations"],"mappings":"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"}
|
package/dist/index.es.js
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
import { resolveCollectionRelations } from "@rebasepro/common";
|
|
2
|
+
function toPascalCase(str) {
|
|
3
|
+
return str.split(/[_\-\s]+/).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("");
|
|
4
|
+
}
|
|
5
|
+
function toCamelCase(str) {
|
|
6
|
+
const pascal = toPascalCase(str);
|
|
7
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
8
|
+
}
|
|
9
|
+
function toSafeIdentifier(str) {
|
|
10
|
+
return toCamelCase(str.replace(/[^a-zA-Z0-9_]/g, "_"));
|
|
11
|
+
}
|
|
12
|
+
function indent(text, spaces) {
|
|
13
|
+
const pad = " ".repeat(spaces);
|
|
14
|
+
return text.split("\n").map((line) => line.trim() ? pad + line : line).join("\n");
|
|
15
|
+
}
|
|
16
|
+
function propertyToTypeScriptType(prop) {
|
|
17
|
+
switch (prop.type) {
|
|
18
|
+
case "string": {
|
|
19
|
+
const sp = prop;
|
|
20
|
+
if (sp.enum) {
|
|
21
|
+
const ids = Array.isArray(sp.enum) ? sp.enum.map((e) => typeof e === "object" ? String(e.id) : String(e)) : Object.keys(sp.enum);
|
|
22
|
+
return ids.map((v) => `"${v}"`).join(" | ");
|
|
23
|
+
}
|
|
24
|
+
return "string";
|
|
25
|
+
}
|
|
26
|
+
case "number": {
|
|
27
|
+
const np = prop;
|
|
28
|
+
if (np.enum) {
|
|
29
|
+
const ids = Array.isArray(np.enum) ? np.enum.map((e) => typeof e === "object" ? String(e.id) : String(e)) : Object.keys(np.enum);
|
|
30
|
+
return ids.join(" | ");
|
|
31
|
+
}
|
|
32
|
+
return "number";
|
|
33
|
+
}
|
|
34
|
+
case "boolean":
|
|
35
|
+
return "boolean";
|
|
36
|
+
case "date":
|
|
37
|
+
return "string";
|
|
38
|
+
// ISO 8601 string over the wire
|
|
39
|
+
case "geopoint":
|
|
40
|
+
return "{ latitude: number; longitude: number; }";
|
|
41
|
+
case "reference":
|
|
42
|
+
return "string | number";
|
|
43
|
+
case "relation":
|
|
44
|
+
return "string | number";
|
|
45
|
+
case "map": {
|
|
46
|
+
const mapProp = prop;
|
|
47
|
+
if (mapProp.properties) {
|
|
48
|
+
const inner = Object.entries(mapProp.properties).map(([k, v]) => `${toSafeIdentifier(k)}: ${propertyToTypeScriptType(v)};`).join(" ");
|
|
49
|
+
return `{ ${inner} }`;
|
|
50
|
+
}
|
|
51
|
+
return "Record<string, any>";
|
|
52
|
+
}
|
|
53
|
+
case "array": {
|
|
54
|
+
const arrProp = prop;
|
|
55
|
+
if (arrProp.of) {
|
|
56
|
+
return `Array<${propertyToTypeScriptType(arrProp.of)}>`;
|
|
57
|
+
}
|
|
58
|
+
return "Array<any>";
|
|
59
|
+
}
|
|
60
|
+
default:
|
|
61
|
+
return "any";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function generateTypedefs(collections) {
|
|
65
|
+
const lines = [
|
|
66
|
+
`/**`,
|
|
67
|
+
` * This file was auto-generated by Rebase.`,
|
|
68
|
+
` * Do not make direct changes to the file.`,
|
|
69
|
+
` */`,
|
|
70
|
+
``,
|
|
71
|
+
`export interface Database {`
|
|
72
|
+
];
|
|
73
|
+
for (const collection of collections) {
|
|
74
|
+
toPascalCase(collection.slug);
|
|
75
|
+
const properties = collection.properties ?? {};
|
|
76
|
+
let resolvedRelations = {};
|
|
77
|
+
try {
|
|
78
|
+
resolvedRelations = resolveCollectionRelations(collection);
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
lines.push(` ${toSafeIdentifier(collection.slug)}: {`);
|
|
82
|
+
lines.push(` Row: {`);
|
|
83
|
+
const emittedKeys = /* @__PURE__ */ new Set();
|
|
84
|
+
for (const [key, rawProp] of Object.entries(properties)) {
|
|
85
|
+
const prop = rawProp;
|
|
86
|
+
if (prop.type === "relation") continue;
|
|
87
|
+
const tsType = propertyToTypeScriptType(prop);
|
|
88
|
+
const isRequired = prop.validation?.required;
|
|
89
|
+
lines.push(` ${toSafeIdentifier(key)}${isRequired ? "" : "?"}: ${tsType};`);
|
|
90
|
+
emittedKeys.add(key);
|
|
91
|
+
}
|
|
92
|
+
for (const [relKey, relation] of Object.entries(resolvedRelations)) {
|
|
93
|
+
if (relation.direction === "owning" && relation.cardinality === "one" && relation.localKey) {
|
|
94
|
+
const fkKey = relation.localKey;
|
|
95
|
+
if (emittedKeys.has(fkKey)) continue;
|
|
96
|
+
let fkType = "string | number";
|
|
97
|
+
try {
|
|
98
|
+
const target = relation.target();
|
|
99
|
+
if (target.properties) {
|
|
100
|
+
const idProp = Object.entries(target.properties).find(([_, p]) => p.isId);
|
|
101
|
+
if (idProp) {
|
|
102
|
+
fkType = idProp[1].type === "number" ? "number" : "string";
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} catch {
|
|
106
|
+
}
|
|
107
|
+
const isRequired = relation.validation?.required;
|
|
108
|
+
lines.push(` ${toSafeIdentifier(fkKey)}${isRequired ? "" : "?"}: ${fkType};`);
|
|
109
|
+
emittedKeys.add(fkKey);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
lines.push(` };`);
|
|
113
|
+
lines.push(` Insert: {`);
|
|
114
|
+
emittedKeys.clear();
|
|
115
|
+
for (const [key, rawProp] of Object.entries(properties)) {
|
|
116
|
+
const prop = rawProp;
|
|
117
|
+
if (prop.type === "relation") continue;
|
|
118
|
+
const tsType = propertyToTypeScriptType(prop);
|
|
119
|
+
const isRequired = prop.validation?.required;
|
|
120
|
+
const typedProp = prop;
|
|
121
|
+
const isAutoId = "isId" in prop && typedProp.isId && typedProp.isId !== "manual" && typedProp.isId !== true;
|
|
122
|
+
const isOptional = !isRequired || isAutoId;
|
|
123
|
+
lines.push(` ${toSafeIdentifier(key)}${isOptional ? "?" : ""}: ${tsType};`);
|
|
124
|
+
emittedKeys.add(key);
|
|
125
|
+
}
|
|
126
|
+
for (const [relKey, relation] of Object.entries(resolvedRelations)) {
|
|
127
|
+
if (relation.direction === "owning" && relation.cardinality === "one" && relation.localKey) {
|
|
128
|
+
const fkKey = relation.localKey;
|
|
129
|
+
if (emittedKeys.has(fkKey)) continue;
|
|
130
|
+
let fkType = "string | number";
|
|
131
|
+
const isRequired = relation.validation?.required;
|
|
132
|
+
lines.push(` ${toSafeIdentifier(fkKey)}${isRequired ? "" : "?"}: ${fkType};`);
|
|
133
|
+
emittedKeys.add(fkKey);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
lines.push(` };`);
|
|
137
|
+
lines.push(` Update: {`);
|
|
138
|
+
emittedKeys.clear();
|
|
139
|
+
for (const [key, rawProp] of Object.entries(properties)) {
|
|
140
|
+
const prop = rawProp;
|
|
141
|
+
if (prop.type === "relation") continue;
|
|
142
|
+
const tsType = propertyToTypeScriptType(prop);
|
|
143
|
+
lines.push(` ${toSafeIdentifier(key)}?: ${tsType};`);
|
|
144
|
+
emittedKeys.add(key);
|
|
145
|
+
}
|
|
146
|
+
for (const [relKey, relation] of Object.entries(resolvedRelations)) {
|
|
147
|
+
if (relation.direction === "owning" && relation.cardinality === "one" && relation.localKey) {
|
|
148
|
+
const fkKey = relation.localKey;
|
|
149
|
+
if (emittedKeys.has(fkKey)) continue;
|
|
150
|
+
lines.push(` ${toSafeIdentifier(fkKey)}?: string | number;`);
|
|
151
|
+
emittedKeys.add(fkKey);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
lines.push(` };`);
|
|
155
|
+
lines.push(` };`);
|
|
156
|
+
}
|
|
157
|
+
lines.push(`}`);
|
|
158
|
+
lines.push(``);
|
|
159
|
+
return lines.join("\n");
|
|
160
|
+
}
|
|
161
|
+
function generateSDK(collections, options = {}) {
|
|
162
|
+
const files = [];
|
|
163
|
+
files.push({
|
|
164
|
+
path: "database.types.ts",
|
|
165
|
+
content: generateTypedefs(collections)
|
|
166
|
+
});
|
|
167
|
+
if (options.includeReadme !== false) {
|
|
168
|
+
files.push({
|
|
169
|
+
path: "README.md",
|
|
170
|
+
content: `# Rebase SDK
|
|
171
|
+
|
|
172
|
+
> Auto-generated by \`rebase generate-sdk\`. Do not edit manually.
|
|
173
|
+
|
|
174
|
+
## Usage
|
|
175
|
+
|
|
176
|
+
1. Install the client package:
|
|
177
|
+
\`\`\`bash
|
|
178
|
+
npm install @rebasepro/client
|
|
179
|
+
\`\`\`
|
|
180
|
+
|
|
181
|
+
2. Initialize with your generated types:
|
|
182
|
+
\`\`\`typescript
|
|
183
|
+
import { createRebaseClient } from '@rebasepro/client';
|
|
184
|
+
import type { Database } from './database.types';
|
|
185
|
+
|
|
186
|
+
const rebase = createRebaseClient<Database>({
|
|
187
|
+
baseUrl: 'http://localhost:3001',
|
|
188
|
+
// token: '...', // User token if not using auth module
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
// Both syntax styles are fully typed!
|
|
192
|
+
const { data: users } = await rebase.data.users.find();
|
|
193
|
+
const { data: posts } = await rebase.data.collection('posts').find();
|
|
194
|
+
\`\`\`
|
|
195
|
+
`
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
return files;
|
|
199
|
+
}
|
|
200
|
+
export {
|
|
201
|
+
generateSDK,
|
|
202
|
+
generateTypedefs,
|
|
203
|
+
indent,
|
|
204
|
+
toCamelCase,
|
|
205
|
+
toPascalCase,
|
|
206
|
+
toSafeIdentifier
|
|
207
|
+
};
|
|
208
|
+
//# sourceMappingURL=index.es.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.es.js","sources":["../src/utils.ts","../src/generate-types.ts","../src/index.ts"],"sourcesContent":["/**\n * Utility functions for the SDK generator\n */\n\n/**\n * Convert a slug/snake_case string to PascalCase\n * e.g. \"private_notes\" → \"PrivateNotes\"\n */\nexport function toPascalCase(str: string): string {\n return str\n .split(/[_\\-\\s]+/)\n .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())\n .join(\"\");\n}\n\n/**\n * Convert a slug/snake_case string to camelCase\n * e.g. \"private_notes\" → \"privateNotes\"\n */\nexport function toCamelCase(str: string): string {\n const pascal = toPascalCase(str);\n return pascal.charAt(0).toLowerCase() + pascal.slice(1);\n}\n\n/**\n * Convert a slug to a safe JS identifier\n * e.g. \"private-notes\" → \"privateNotes\"\n */\nexport function toSafeIdentifier(str: string): string {\n return toCamelCase(str.replace(/[^a-zA-Z0-9_]/g, \"_\"));\n}\n\n/**\n * Indent a block of text by a given number of spaces\n */\nexport function indent(text: string, spaces: number): string {\n const pad = \" \".repeat(spaces);\n return text\n .split(\"\\n\")\n .map(line => (line.trim() ? pad + line : line))\n .join(\"\\n\");\n}\n","import { EntityCollection, PostgresCollection, Property, Properties, MapProperty, ArrayProperty, RelationProperty, StringProperty, NumberProperty } from \"@rebasepro/types\";\nimport { resolveCollectionRelations } from \"@rebasepro/common\";\nimport { toPascalCase, toSafeIdentifier } from \"./utils\";\n\nfunction propertyToTypeScriptType(prop: Property): string {\n switch (prop.type) {\n case \"string\": {\n const sp = prop as StringProperty;\n if (sp.enum) {\n const ids = Array.isArray(sp.enum)\n ? sp.enum.map((e: any) => typeof e === \"object\" ? String(e.id) : String(e))\n : Object.keys(sp.enum);\n return ids.map(v => `\"${v}\"`).join(\" | \");\n }\n return \"string\";\n }\n case \"number\": {\n const np = prop as NumberProperty;\n if (np.enum) {\n const ids = Array.isArray(np.enum)\n ? np.enum.map((e: any) => typeof e === \"object\" ? String(e.id) : String(e))\n : Object.keys(np.enum);\n return ids.join(\" | \");\n }\n return \"number\";\n }\n case \"boolean\":\n return \"boolean\";\n case \"date\":\n return \"string\"; // ISO 8601 string over the wire\n case \"geopoint\":\n return \"{ latitude: number; longitude: number; }\";\n case \"reference\":\n return \"string | number\";\n case \"relation\":\n return \"string | number\";\n case \"map\": {\n const mapProp = prop as MapProperty;\n if (mapProp.properties) {\n const inner = Object.entries(mapProp.properties)\n .map(([k, v]) => `${toSafeIdentifier(k)}: ${propertyToTypeScriptType(v as Property)};`)\n .join(\" \");\n return `{ ${inner} }`;\n }\n return \"Record<string, any>\";\n }\n case \"array\": {\n const arrProp = prop as ArrayProperty;\n if (arrProp.of) {\n return `Array<${propertyToTypeScriptType(arrProp.of as Property)}>`;\n }\n return \"Array<any>\";\n }\n default:\n return \"any\";\n }\n}\n\nexport function generateTypedefs(collections: EntityCollection[]): string {\n const lines: string[] = [\n `/**`,\n ` * This file was auto-generated by Rebase.`,\n ` * Do not make direct changes to the file.`,\n ` */`,\n ``,\n `export interface Database {`\n ];\n\n for (const collection of collections) {\n const typeName = toPascalCase(collection.slug);\n const properties = (collection.properties ?? {}) as Properties;\n\n // Resolve relations\n let resolvedRelations: Record<string, any> = {};\n try {\n resolvedRelations = resolveCollectionRelations(collection as PostgresCollection);\n } catch { /* ignore */ }\n\n lines.push(` ${toSafeIdentifier(collection.slug)}: {`);\n \n // ── Row Type ──\n lines.push(` Row: {`);\n const emittedKeys = new Set<string>();\n\n // 1. Direct properties\n for (const [key, rawProp] of Object.entries(properties)) {\n const prop = rawProp as Property;\n if (prop.type === \"relation\") continue; \n\n const tsType = propertyToTypeScriptType(prop);\n const isRequired = prop.validation?.required;\n lines.push(` ${toSafeIdentifier(key)}${isRequired ? \"\" : \"?\"}: ${tsType};`);\n emittedKeys.add(key);\n }\n \n // 2. FK columns from relations\n for (const [relKey, relation] of Object.entries(resolvedRelations)) {\n if (relation.direction === \"owning\" && relation.cardinality === \"one\" && relation.localKey) {\n const fkKey = relation.localKey;\n if (emittedKeys.has(fkKey)) continue;\n\n let fkType = \"string | number\";\n try {\n const target = relation.target();\n if (target.properties) {\n const idProp = Object.entries(target.properties).find(([_, p]: [string, any]) => p.isId);\n if (idProp) {\n fkType = (idProp[1] as Property).type === \"number\" ? \"number\" : \"string\";\n }\n }\n } catch { /* ignore */ }\n\n const isRequired = relation.validation?.required;\n lines.push(` ${toSafeIdentifier(fkKey)}${isRequired ? \"\" : \"?\"}: ${fkType};`);\n emittedKeys.add(fkKey);\n }\n }\n lines.push(` };`);\n\n // ── Insert Type ──\n lines.push(` Insert: {`);\n emittedKeys.clear();\n\n for (const [key, rawProp] of Object.entries(properties)) {\n const prop = rawProp as Property;\n if (prop.type === \"relation\") continue; \n const tsType = propertyToTypeScriptType(prop);\n const isRequired = prop.validation?.required;\n const typedProp = prop as StringProperty | NumberProperty;\n const isAutoId = \"isId\" in prop && typedProp.isId && typedProp.isId !== \"manual\" && typedProp.isId !== true;\n const isOptional = !isRequired || isAutoId;\n lines.push(` ${toSafeIdentifier(key)}${isOptional ? \"?\" : \"\"}: ${tsType};`);\n emittedKeys.add(key);\n }\n\n for (const [relKey, relation] of Object.entries(resolvedRelations)) {\n if (relation.direction === \"owning\" && relation.cardinality === \"one\" && relation.localKey) {\n const fkKey = relation.localKey;\n if (emittedKeys.has(fkKey)) continue;\n let fkType = \"string | number\";\n // simple fallback\n const isRequired = relation.validation?.required;\n lines.push(` ${toSafeIdentifier(fkKey)}${isRequired ? \"\" : \"?\"}: ${fkType};`);\n emittedKeys.add(fkKey);\n }\n }\n lines.push(` };`);\n\n // ── Update Type ──\n lines.push(` Update: {`);\n emittedKeys.clear();\n for (const [key, rawProp] of Object.entries(properties)) {\n const prop = rawProp as Property;\n if (prop.type === \"relation\") continue; \n const tsType = propertyToTypeScriptType(prop);\n lines.push(` ${toSafeIdentifier(key)}?: ${tsType};`);\n emittedKeys.add(key);\n }\n for (const [relKey, relation] of Object.entries(resolvedRelations)) {\n if (relation.direction === \"owning\" && relation.cardinality === \"one\" && relation.localKey) {\n const fkKey = relation.localKey;\n if (emittedKeys.has(fkKey)) continue;\n lines.push(` ${toSafeIdentifier(fkKey)}?: string | number;`);\n emittedKeys.add(fkKey);\n }\n }\n lines.push(` };`);\n\n lines.push(` };`);\n }\n\n lines.push(`}`);\n lines.push(``);\n\n return lines.join(\"\\n\");\n}\n","/**\n * @rebasepro/sdk-generator\n *\n * Generates a purely typed Typescript database definition.\n */\n\nimport { EntityCollection } from \"@rebasepro/types\";\nimport { generateTypedefs } from \"./generate-types\";\n\nexport { generateTypedefs } from \"./generate-types\";\nexport { toPascalCase, toCamelCase, toSafeIdentifier, indent } from \"./utils\";\n\n// ─── Public API ────────────────────────────────────────────────────\n\nexport interface GeneratedFile {\n /** Relative file path within the output directory */\n path: string;\n /** File content */\n content: string;\n}\n\nexport interface GenerateSDKOptions {\n /** Whether to include a README file (default: true) */\n includeReadme?: boolean;\n}\n\nexport function generateSDK(\n collections: EntityCollection[],\n options: GenerateSDKOptions = {},\n): GeneratedFile[] {\n const files: GeneratedFile[] = [];\n\n files.push({\n path: \"database.types.ts\",\n content: generateTypedefs(collections)\n });\n\n if (options.includeReadme !== false) {\n files.push({\n path: \"README.md\",\n content: `# Rebase SDK\n\n> Auto-generated by \\`rebase generate-sdk\\`. Do not edit manually.\n\n## Usage\n\n1. Install the client package:\n \\`\\`\\`bash\n npm install @rebasepro/client\n \\`\\`\\`\n\n2. Initialize with your generated types:\n \\`\\`\\`typescript\n import { createRebaseClient } from '@rebasepro/client';\n import type { Database } from './database.types';\n\n const rebase = createRebaseClient<Database>({\n baseUrl: 'http://localhost:3001',\n // token: '...', // User token if not using auth module\n });\n\n // Both syntax styles are fully typed!\n const { data: users } = await rebase.data.users.find();\n const { data: posts } = await rebase.data.collection('posts').find();\n \\`\\`\\`\n`\n });\n }\n\n return files;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rebasepro/sdk-generator
|
|
3
|
+
*
|
|
4
|
+
* Generates a purely typed Typescript database definition.
|
|
5
|
+
*/
|
|
6
|
+
import { EntityCollection } from "@rebasepro/types";
|
|
7
|
+
export { generateTypedefs } from "./generate-types";
|
|
8
|
+
export { toPascalCase, toCamelCase, toSafeIdentifier, indent } from "./utils";
|
|
9
|
+
export interface GeneratedFile {
|
|
10
|
+
/** Relative file path within the output directory */
|
|
11
|
+
path: string;
|
|
12
|
+
/** File content */
|
|
13
|
+
content: string;
|
|
14
|
+
}
|
|
15
|
+
export interface GenerateSDKOptions {
|
|
16
|
+
/** Whether to include a README file (default: true) */
|
|
17
|
+
includeReadme?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export declare function generateSDK(collections: EntityCollection[], options?: GenerateSDKOptions): GeneratedFile[];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for the SDK generator
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Convert a slug/snake_case string to PascalCase
|
|
6
|
+
* e.g. "private_notes" → "PrivateNotes"
|
|
7
|
+
*/
|
|
8
|
+
export declare function toPascalCase(str: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Convert a slug/snake_case string to camelCase
|
|
11
|
+
* e.g. "private_notes" → "privateNotes"
|
|
12
|
+
*/
|
|
13
|
+
export declare function toCamelCase(str: string): string;
|
|
14
|
+
/**
|
|
15
|
+
* Convert a slug to a safe JS identifier
|
|
16
|
+
* e.g. "private-notes" → "privateNotes"
|
|
17
|
+
*/
|
|
18
|
+
export declare function toSafeIdentifier(str: string): string;
|
|
19
|
+
/**
|
|
20
|
+
* Indent a block of text by a given number of spaces
|
|
21
|
+
*/
|
|
22
|
+
export declare function indent(text: string, spaces: number): string;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type AnalyticsController = {
|
|
2
|
+
/**
|
|
3
|
+
* Callback used to get analytics events from the CMS
|
|
4
|
+
*/
|
|
5
|
+
onAnalyticsEvent?: (event: AnalyticsEvent, data?: object) => void;
|
|
6
|
+
};
|
|
7
|
+
export type AnalyticsEvent = "entity_click" | "entity_click_from_reference" | "reference_selection_clear" | "reference_selection_toggle" | "reference_selected_single" | "reference_selection_new_entity" | "edit_entity_clicked" | "entity_edited" | "new_entity_click" | "new_entity_saved" | "copy_entity_click" | "entity_copied" | "single_delete_dialog_open" | "multiple_delete_dialog_open" | "single_entity_deleted" | "multiple_entities_deleted" | "drawer_navigate_to_home" | "drawer_navigate_to_collection" | "drawer_navigate_to_view" | "home_navigate_to_collection" | "home_favorite_navigate_to_collection" | "home_navigate_to_view" | "home_navigate_to_admin_view" | "home_favorite_navigate_to_view" | "home_move_card" | "home_move_group" | "home_drop_new_group" | "collection_inline_editing" | "view_mode_changed" | "kanban_card_moved" | "kanban_column_reorder" | "kanban_property_changed" | "kanban_new_entity_in_column" | "kanban_backfill_order" | "card_view_entity_click" | "unmapped_event";
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { StorageSource } from "./storage";
|
|
2
|
+
import { Role, User } from "../users";
|
|
3
|
+
import { RebaseData } from "./data";
|
|
4
|
+
/**
|
|
5
|
+
* Capabilities advertised by an auth provider.
|
|
6
|
+
* UI components use this to show/hide features dynamically
|
|
7
|
+
* (e.g. password reset, registration, session management).
|
|
8
|
+
* @group Hooks and utilities
|
|
9
|
+
*/
|
|
10
|
+
export interface AuthCapabilities {
|
|
11
|
+
emailPasswordLogin?: boolean;
|
|
12
|
+
googleLogin?: boolean;
|
|
13
|
+
registration?: boolean;
|
|
14
|
+
passwordReset?: boolean;
|
|
15
|
+
sessionManagement?: boolean;
|
|
16
|
+
profileUpdate?: boolean;
|
|
17
|
+
emailVerification?: boolean;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Controller for retrieving the logged user or performing auth related operations.
|
|
21
|
+
* Note that if you are implementing your AuthController, you probably will want
|
|
22
|
+
* to do it as the result of a hook.
|
|
23
|
+
* @group Hooks and utilities
|
|
24
|
+
*/
|
|
25
|
+
export type AuthController<USER extends User = User, ExtraData = unknown> = {
|
|
26
|
+
/**
|
|
27
|
+
* The user currently logged in
|
|
28
|
+
* The values can be: the user object, null if they skipped login
|
|
29
|
+
*/
|
|
30
|
+
user: USER | null;
|
|
31
|
+
/**
|
|
32
|
+
* Initial loading flag. It is used not to display the login screen
|
|
33
|
+
* when the app first loads, and it has not been checked whether the user
|
|
34
|
+
* is logged in or not.
|
|
35
|
+
*/
|
|
36
|
+
initialLoading?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Loading flag. It is used to display a loading screen when the user is
|
|
39
|
+
* logging in or out.
|
|
40
|
+
*/
|
|
41
|
+
authLoading: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Sign out
|
|
44
|
+
*/
|
|
45
|
+
signOut: () => Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Error initializing the authentication
|
|
48
|
+
*/
|
|
49
|
+
authError?: unknown;
|
|
50
|
+
/**
|
|
51
|
+
* Error dispatched by the auth provider
|
|
52
|
+
*/
|
|
53
|
+
authProviderError?: unknown;
|
|
54
|
+
/**
|
|
55
|
+
* You can use this method to retrieve the auth token for the current user.
|
|
56
|
+
*/
|
|
57
|
+
getAuthToken: () => Promise<string>;
|
|
58
|
+
/**
|
|
59
|
+
* Has the user skipped the login process
|
|
60
|
+
*/
|
|
61
|
+
loginSkipped: boolean;
|
|
62
|
+
extra: ExtraData;
|
|
63
|
+
setExtra: (extra: ExtraData) => void;
|
|
64
|
+
setUser?(user: USER | null): void;
|
|
65
|
+
setUserRoles?(roles: Role[]): void;
|
|
66
|
+
/**
|
|
67
|
+
* Capabilities advertised by the auth provider.
|
|
68
|
+
* UI components use this to feature-detect what the backend supports.
|
|
69
|
+
*/
|
|
70
|
+
capabilities?: AuthCapabilities;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Extended auth controller with common optional auth methods.
|
|
74
|
+
* Backend implementations (Rebase backend, Firebase, Supabase, etc.)
|
|
75
|
+
* extend this with their own backend-specific extras.
|
|
76
|
+
* @group Hooks and utilities
|
|
77
|
+
*/
|
|
78
|
+
export interface AuthControllerExtended<USER extends User = User, ExtraData = unknown> extends AuthController<USER, ExtraData> {
|
|
79
|
+
/** Login with email and password */
|
|
80
|
+
emailPasswordLogin?(email: string, password: string): Promise<void>;
|
|
81
|
+
/** Login with a Google ID token or trigger Google popup */
|
|
82
|
+
googleLogin?(idToken: string): Promise<void>;
|
|
83
|
+
/** Register a new user */
|
|
84
|
+
register?(email: string, password: string, displayName?: string): Promise<void>;
|
|
85
|
+
/** Skip login (for anonymous access if enabled) */
|
|
86
|
+
skipLogin?(): void;
|
|
87
|
+
/** Request password reset email */
|
|
88
|
+
forgotPassword?(email: string): Promise<void>;
|
|
89
|
+
/** Reset password using a token */
|
|
90
|
+
resetPassword?(token: string, password: string): Promise<void>;
|
|
91
|
+
/** Change password for the authenticated user */
|
|
92
|
+
changePassword?(oldPassword: string, newPassword: string): Promise<void>;
|
|
93
|
+
/** Update user profile */
|
|
94
|
+
updateProfile?(displayName?: string, photoURL?: string): Promise<USER>;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Implement this function to allow access to specific users.
|
|
98
|
+
* @group Hooks and utilities
|
|
99
|
+
*/
|
|
100
|
+
export type Authenticator<USER extends User = User> = (props: {
|
|
101
|
+
/**
|
|
102
|
+
* Logged-in user or null
|
|
103
|
+
*/
|
|
104
|
+
user: USER | null;
|
|
105
|
+
/**
|
|
106
|
+
* AuthController
|
|
107
|
+
*/
|
|
108
|
+
authController: AuthController<USER>;
|
|
109
|
+
/**
|
|
110
|
+
* Unified data access API
|
|
111
|
+
*/
|
|
112
|
+
data: RebaseData;
|
|
113
|
+
/**
|
|
114
|
+
* Used storage implementation
|
|
115
|
+
*/
|
|
116
|
+
storageSource: StorageSource;
|
|
117
|
+
}) => boolean | Promise<boolean>;
|