@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
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { Role, User } from "../users";
|
|
2
|
+
/**
|
|
3
|
+
* Result of creating a new user via admin flow.
|
|
4
|
+
* Contains the created user plus information about how credentials were delivered.
|
|
5
|
+
*/
|
|
6
|
+
export interface UserCreationResult<USER extends User = User> {
|
|
7
|
+
/** The created user */
|
|
8
|
+
user: USER;
|
|
9
|
+
/** Whether an invitation email was sent to the user */
|
|
10
|
+
invitationSent: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* Temporary password (only present when email service is not configured).
|
|
13
|
+
* This is returned one-time and should be shown to the admin to share manually.
|
|
14
|
+
*/
|
|
15
|
+
temporaryPassword?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Delegate to manage users, roles, and their permissions.
|
|
19
|
+
* This interface allows the CMS to be completely agnostic of the underlying
|
|
20
|
+
* authentication provider or backend.
|
|
21
|
+
*
|
|
22
|
+
* @group Models
|
|
23
|
+
*/
|
|
24
|
+
export interface UserManagementDelegate<USER extends User = User> {
|
|
25
|
+
/**
|
|
26
|
+
* Are the users and roles currently being fetched?
|
|
27
|
+
*/
|
|
28
|
+
loading: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* List of users managed by the CMS.
|
|
31
|
+
*/
|
|
32
|
+
users: USER[];
|
|
33
|
+
/**
|
|
34
|
+
* Optional error if users failed to load.
|
|
35
|
+
*/
|
|
36
|
+
usersError?: Error;
|
|
37
|
+
/**
|
|
38
|
+
* Function to get a user by its uid. This is used to show
|
|
39
|
+
* user information when assigning ownership of an entity.
|
|
40
|
+
* @param uid
|
|
41
|
+
*/
|
|
42
|
+
getUser: (uid: string) => USER | null;
|
|
43
|
+
/**
|
|
44
|
+
* Search users with server-side pagination.
|
|
45
|
+
* When provided, the CMS will use this for the users table
|
|
46
|
+
* instead of loading all users into memory.
|
|
47
|
+
*/
|
|
48
|
+
searchUsers?: (options: {
|
|
49
|
+
search?: string;
|
|
50
|
+
limit?: number;
|
|
51
|
+
offset?: number;
|
|
52
|
+
orderBy?: string;
|
|
53
|
+
orderDir?: "asc" | "desc";
|
|
54
|
+
}) => Promise<{
|
|
55
|
+
users: USER[];
|
|
56
|
+
total: number;
|
|
57
|
+
}>;
|
|
58
|
+
/**
|
|
59
|
+
* Save a user (create or update)
|
|
60
|
+
* @param user
|
|
61
|
+
*/
|
|
62
|
+
saveUser?: (user: USER) => Promise<USER>;
|
|
63
|
+
/**
|
|
64
|
+
* Create a new user with invitation/password generation support.
|
|
65
|
+
* Returns additional info about how the credentials were delivered.
|
|
66
|
+
* Falls back to saveUser if not provided.
|
|
67
|
+
*/
|
|
68
|
+
createUser?: (user: USER) => Promise<UserCreationResult<USER>>;
|
|
69
|
+
/**
|
|
70
|
+
* Reset the password for an existing user.
|
|
71
|
+
* Returns a temporary password if no email service is configured,
|
|
72
|
+
* or a flag indicating an email invitation was sent.
|
|
73
|
+
*/
|
|
74
|
+
resetPassword?: (user: USER) => Promise<UserCreationResult<USER>>;
|
|
75
|
+
/**
|
|
76
|
+
* Delete a user
|
|
77
|
+
* @param user
|
|
78
|
+
*/
|
|
79
|
+
deleteUser?: (user: USER) => Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* List of roles defined in the CMS.
|
|
82
|
+
*/
|
|
83
|
+
roles?: Role[];
|
|
84
|
+
/**
|
|
85
|
+
* Optional error if roles failed to load.
|
|
86
|
+
*/
|
|
87
|
+
rolesError?: Error;
|
|
88
|
+
/**
|
|
89
|
+
* Save a role (create or update)
|
|
90
|
+
* @param role
|
|
91
|
+
*/
|
|
92
|
+
saveRole?: (role: Role) => Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Delete a role
|
|
95
|
+
* @param role
|
|
96
|
+
*/
|
|
97
|
+
deleteRole?: (role: Role) => Promise<void>;
|
|
98
|
+
/**
|
|
99
|
+
* Is the currently logged in user an admin?
|
|
100
|
+
*/
|
|
101
|
+
isAdmin?: boolean;
|
|
102
|
+
/**
|
|
103
|
+
* If true, the UI will allow the user to create the default roles (admin, editor, viewer).
|
|
104
|
+
*/
|
|
105
|
+
allowDefaultRolesCreation?: boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Should collection config permissions be included?
|
|
108
|
+
*/
|
|
109
|
+
includeCollectionConfigPermissions?: boolean;
|
|
110
|
+
/**
|
|
111
|
+
* Optionally define roles for a given user. This is useful when the roles
|
|
112
|
+
* are coming from a separate provider than the one issuing the tokens.
|
|
113
|
+
*/
|
|
114
|
+
defineRolesFor?: (user: USER) => Promise<Role[] | undefined> | Role[] | undefined;
|
|
115
|
+
/**
|
|
116
|
+
* Optional function to bootstrap an admin user.
|
|
117
|
+
* Often used when the database is empty.
|
|
118
|
+
*/
|
|
119
|
+
bootstrapAdmin?: () => Promise<void>;
|
|
120
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Entity } from "./entities";
|
|
2
|
+
export interface WebSocketErrorPayload {
|
|
3
|
+
error?: string | {
|
|
4
|
+
message: string;
|
|
5
|
+
code?: string;
|
|
6
|
+
};
|
|
7
|
+
message?: string;
|
|
8
|
+
code?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface WebSocketMessage {
|
|
11
|
+
type: string;
|
|
12
|
+
payload?: unknown;
|
|
13
|
+
subscriptionId?: string;
|
|
14
|
+
requestId?: string;
|
|
15
|
+
entities?: Entity<Record<string, unknown>>[];
|
|
16
|
+
entity?: Entity<Record<string, unknown>> | null;
|
|
17
|
+
error?: string;
|
|
18
|
+
}
|
|
19
|
+
export interface CollectionUpdateMessage extends WebSocketMessage {
|
|
20
|
+
type: "collection_update";
|
|
21
|
+
subscriptionId: string;
|
|
22
|
+
entities: Entity<Record<string, unknown>>[];
|
|
23
|
+
}
|
|
24
|
+
export interface EntityUpdateMessage extends WebSocketMessage {
|
|
25
|
+
type: "entity_update";
|
|
26
|
+
subscriptionId: string;
|
|
27
|
+
entity: Entity<Record<string, unknown>> | null;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Lightweight patch message sent to collection subscribers when a single
|
|
31
|
+
* entity is created, updated, or deleted. The client can merge this into
|
|
32
|
+
* its cached collection data for near-instant cross-tab updates without
|
|
33
|
+
* waiting for a full collection refetch.
|
|
34
|
+
*/
|
|
35
|
+
export interface CollectionEntityPatchMessage extends WebSocketMessage {
|
|
36
|
+
type: "collection_entity_patch";
|
|
37
|
+
subscriptionId: string;
|
|
38
|
+
entityId: string;
|
|
39
|
+
/** The updated entity, or null if deleted */
|
|
40
|
+
entity: Entity<Record<string, unknown>> | null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Column metadata returned by table introspection.
|
|
44
|
+
*/
|
|
45
|
+
export interface TableColumnInfo {
|
|
46
|
+
column_name: string;
|
|
47
|
+
data_type: string;
|
|
48
|
+
udt_name: string;
|
|
49
|
+
is_nullable: string;
|
|
50
|
+
column_default: string | null;
|
|
51
|
+
character_maximum_length: number | null;
|
|
52
|
+
/** Enum values, populated for USER-DEFINED (enum) columns */
|
|
53
|
+
enum_values?: string[];
|
|
54
|
+
}
|
|
55
|
+
export interface TableForeignKeyInfo {
|
|
56
|
+
column_name: string;
|
|
57
|
+
foreign_table_name: string;
|
|
58
|
+
foreign_column_name: string;
|
|
59
|
+
}
|
|
60
|
+
export interface TableJunctionInfo {
|
|
61
|
+
junction_table_name: string;
|
|
62
|
+
source_column_name: string;
|
|
63
|
+
target_table_name: string;
|
|
64
|
+
target_column_name: string;
|
|
65
|
+
}
|
|
66
|
+
export interface TablePolicyInfo {
|
|
67
|
+
policy_name: string;
|
|
68
|
+
roles: string[];
|
|
69
|
+
cmd: string;
|
|
70
|
+
qual?: string;
|
|
71
|
+
with_check?: string;
|
|
72
|
+
}
|
|
73
|
+
export interface TableMetadata {
|
|
74
|
+
columns: TableColumnInfo[];
|
|
75
|
+
foreignKeys: TableForeignKeyInfo[];
|
|
76
|
+
junctions: TableJunctionInfo[];
|
|
77
|
+
policies: TablePolicyInfo[];
|
|
78
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export type Role = {
|
|
2
|
+
/**
|
|
3
|
+
* ID of the role
|
|
4
|
+
*/
|
|
5
|
+
id: string;
|
|
6
|
+
/**
|
|
7
|
+
* Name of the role
|
|
8
|
+
*/
|
|
9
|
+
name: string;
|
|
10
|
+
/**
|
|
11
|
+
* If this flag is true, the user can perform any action
|
|
12
|
+
*/
|
|
13
|
+
isAdmin?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Permissions related to editing the collections
|
|
16
|
+
*/
|
|
17
|
+
config?: {
|
|
18
|
+
createCollections?: boolean;
|
|
19
|
+
editCollections?: boolean | "own";
|
|
20
|
+
deleteCollections?: boolean | "own";
|
|
21
|
+
};
|
|
22
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This interface represents a user.
|
|
3
|
+
* It has some of the same fields as a Firebase User.
|
|
4
|
+
* Note that in the default implementation, we simply take the Firebase user
|
|
5
|
+
* and use it as a Rebase user, so that means that even if they are not mapped
|
|
6
|
+
* in this interface, it contains all the methods of the former, such as `delete`,
|
|
7
|
+
* `getIdToken`, etc.
|
|
8
|
+
*
|
|
9
|
+
* @group Models
|
|
10
|
+
*/
|
|
11
|
+
export type User = {
|
|
12
|
+
/**
|
|
13
|
+
* The user's unique ID, scoped to the project.
|
|
14
|
+
*/
|
|
15
|
+
readonly uid: string;
|
|
16
|
+
/**
|
|
17
|
+
* The display name of the user.
|
|
18
|
+
*/
|
|
19
|
+
readonly displayName: string | null;
|
|
20
|
+
/**
|
|
21
|
+
* The email of the user.
|
|
22
|
+
*/
|
|
23
|
+
readonly email: string | null;
|
|
24
|
+
/**
|
|
25
|
+
* The profile photo URL of the user.
|
|
26
|
+
*/
|
|
27
|
+
readonly photoURL: string | null;
|
|
28
|
+
/**
|
|
29
|
+
* The provider used to authenticate the user.
|
|
30
|
+
*/
|
|
31
|
+
readonly providerId: string;
|
|
32
|
+
/**
|
|
33
|
+
*
|
|
34
|
+
*/
|
|
35
|
+
readonly isAnonymous: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Role IDs assigned to this user (e.g. ["admin", "editor"]).
|
|
38
|
+
* These are plain string IDs — use the UserManagementDelegate to look up full Role objects.
|
|
39
|
+
*/
|
|
40
|
+
roles?: string[];
|
|
41
|
+
/**
|
|
42
|
+
* The date and time when the user was created.
|
|
43
|
+
*/
|
|
44
|
+
createdAt?: Date | string | null;
|
|
45
|
+
getIdToken?: (forceRefresh?: boolean) => Promise<string>;
|
|
46
|
+
};
|
package/jest.config.cjs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** @type {import("ts-jest").JestConfigWithTsJest} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
preset: "ts-jest",
|
|
4
|
+
testEnvironment: "node",
|
|
5
|
+
testMatch: [
|
|
6
|
+
"**/test/**/*.test.ts",
|
|
7
|
+
"**/src/**/*.test.ts"
|
|
8
|
+
],
|
|
9
|
+
moduleNameMapper: {
|
|
10
|
+
"^@rebasepro/types$": "<rootDir>/../types/src",
|
|
11
|
+
"^@rebasepro/common$": "<rootDir>/../common/src",
|
|
12
|
+
}
|
|
13
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rebasepro/sdk-generator",
|
|
3
|
+
"version": "0.0.1-canary.4d4fb3e",
|
|
4
|
+
"description": "Generate a typed JS SDK from Rebase collection definitions",
|
|
5
|
+
"main": "./dist/index.cjs",
|
|
6
|
+
"module": "./dist/index.es.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"source": "src/index.ts",
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"access": "public"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"test": "jest --config jest.config.cjs",
|
|
15
|
+
"build": "vite build && tsc --emitDeclarationOnly -p tsconfig.json",
|
|
16
|
+
"clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"sdk",
|
|
20
|
+
"codegen",
|
|
21
|
+
"rebase",
|
|
22
|
+
"rest-api"
|
|
23
|
+
],
|
|
24
|
+
"author": "rebase.pro",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"peerDependencies": {
|
|
27
|
+
"@rebasepro/common": "workspace:*",
|
|
28
|
+
"@rebasepro/types": "workspace:*"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"@rebasepro/common": "0.0.1-canary.4d4fb3e",
|
|
32
|
+
"@rebasepro/types": "0.0.1-canary.4d4fb3e",
|
|
33
|
+
"@types/jest": "^29.5.14",
|
|
34
|
+
"@types/node": "^20.19.17",
|
|
35
|
+
"jest": "^29.7.0",
|
|
36
|
+
"ts-jest": "^29.3.4",
|
|
37
|
+
"typescript": "^5.9.3",
|
|
38
|
+
"vite": "^7.2.4"
|
|
39
|
+
},
|
|
40
|
+
"exports": {
|
|
41
|
+
".": {
|
|
42
|
+
"types": "./dist/index.d.ts",
|
|
43
|
+
"import": "./dist/index.es.js",
|
|
44
|
+
"require": "./dist/index.cjs"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
"gitHead": "71bcef3c51a458cd054f7924cc18efbbe515dcc8",
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"@rebasepro/client": "0.0.1-canary.4d4fb3e"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { EntityCollection, PostgresCollection, Property, Properties, MapProperty, ArrayProperty, RelationProperty, StringProperty, NumberProperty } from "@rebasepro/types";
|
|
2
|
+
import { resolveCollectionRelations } from "@rebasepro/common";
|
|
3
|
+
import { toPascalCase, toSafeIdentifier } from "./utils";
|
|
4
|
+
|
|
5
|
+
function propertyToTypeScriptType(prop: Property): string {
|
|
6
|
+
switch (prop.type) {
|
|
7
|
+
case "string": {
|
|
8
|
+
const sp = prop as StringProperty;
|
|
9
|
+
if (sp.enum) {
|
|
10
|
+
const ids = Array.isArray(sp.enum)
|
|
11
|
+
? sp.enum.map((e: any) => typeof e === "object" ? String(e.id) : String(e))
|
|
12
|
+
: Object.keys(sp.enum);
|
|
13
|
+
return ids.map(v => `"${v}"`).join(" | ");
|
|
14
|
+
}
|
|
15
|
+
return "string";
|
|
16
|
+
}
|
|
17
|
+
case "number": {
|
|
18
|
+
const np = prop as NumberProperty;
|
|
19
|
+
if (np.enum) {
|
|
20
|
+
const ids = Array.isArray(np.enum)
|
|
21
|
+
? np.enum.map((e: any) => typeof e === "object" ? String(e.id) : String(e))
|
|
22
|
+
: Object.keys(np.enum);
|
|
23
|
+
return ids.join(" | ");
|
|
24
|
+
}
|
|
25
|
+
return "number";
|
|
26
|
+
}
|
|
27
|
+
case "boolean":
|
|
28
|
+
return "boolean";
|
|
29
|
+
case "date":
|
|
30
|
+
return "string"; // ISO 8601 string over the wire
|
|
31
|
+
case "geopoint":
|
|
32
|
+
return "{ latitude: number; longitude: number; }";
|
|
33
|
+
case "reference":
|
|
34
|
+
return "string | number";
|
|
35
|
+
case "relation":
|
|
36
|
+
return "string | number";
|
|
37
|
+
case "map": {
|
|
38
|
+
const mapProp = prop as MapProperty;
|
|
39
|
+
if (mapProp.properties) {
|
|
40
|
+
const inner = Object.entries(mapProp.properties)
|
|
41
|
+
.map(([k, v]) => `${toSafeIdentifier(k)}: ${propertyToTypeScriptType(v as Property)};`)
|
|
42
|
+
.join(" ");
|
|
43
|
+
return `{ ${inner} }`;
|
|
44
|
+
}
|
|
45
|
+
return "Record<string, any>";
|
|
46
|
+
}
|
|
47
|
+
case "array": {
|
|
48
|
+
const arrProp = prop as ArrayProperty;
|
|
49
|
+
if (arrProp.of) {
|
|
50
|
+
return `Array<${propertyToTypeScriptType(arrProp.of as Property)}>`;
|
|
51
|
+
}
|
|
52
|
+
return "Array<any>";
|
|
53
|
+
}
|
|
54
|
+
default:
|
|
55
|
+
return "any";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function generateTypedefs(collections: EntityCollection[]): string {
|
|
60
|
+
const lines: string[] = [
|
|
61
|
+
`/**`,
|
|
62
|
+
` * This file was auto-generated by Rebase.`,
|
|
63
|
+
` * Do not make direct changes to the file.`,
|
|
64
|
+
` */`,
|
|
65
|
+
``,
|
|
66
|
+
`export interface Database {`
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
for (const collection of collections) {
|
|
70
|
+
const typeName = toPascalCase(collection.slug);
|
|
71
|
+
const properties = (collection.properties ?? {}) as Properties;
|
|
72
|
+
|
|
73
|
+
// Resolve relations
|
|
74
|
+
let resolvedRelations: Record<string, any> = {};
|
|
75
|
+
try {
|
|
76
|
+
resolvedRelations = resolveCollectionRelations(collection as PostgresCollection);
|
|
77
|
+
} catch { /* ignore */ }
|
|
78
|
+
|
|
79
|
+
lines.push(` ${toSafeIdentifier(collection.slug)}: {`);
|
|
80
|
+
|
|
81
|
+
// ── Row Type ──
|
|
82
|
+
lines.push(` Row: {`);
|
|
83
|
+
const emittedKeys = new Set<string>();
|
|
84
|
+
|
|
85
|
+
// 1. Direct properties
|
|
86
|
+
for (const [key, rawProp] of Object.entries(properties)) {
|
|
87
|
+
const prop = rawProp as Property;
|
|
88
|
+
if (prop.type === "relation") continue;
|
|
89
|
+
|
|
90
|
+
const tsType = propertyToTypeScriptType(prop);
|
|
91
|
+
const isRequired = prop.validation?.required;
|
|
92
|
+
lines.push(` ${toSafeIdentifier(key)}${isRequired ? "" : "?"}: ${tsType};`);
|
|
93
|
+
emittedKeys.add(key);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 2. FK columns from relations
|
|
97
|
+
for (const [relKey, relation] of Object.entries(resolvedRelations)) {
|
|
98
|
+
if (relation.direction === "owning" && relation.cardinality === "one" && relation.localKey) {
|
|
99
|
+
const fkKey = relation.localKey;
|
|
100
|
+
if (emittedKeys.has(fkKey)) continue;
|
|
101
|
+
|
|
102
|
+
let fkType = "string | number";
|
|
103
|
+
try {
|
|
104
|
+
const target = relation.target();
|
|
105
|
+
if (target.properties) {
|
|
106
|
+
const idProp = Object.entries(target.properties).find(([_, p]: [string, any]) => p.isId);
|
|
107
|
+
if (idProp) {
|
|
108
|
+
fkType = (idProp[1] as Property).type === "number" ? "number" : "string";
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch { /* ignore */ }
|
|
112
|
+
|
|
113
|
+
const isRequired = relation.validation?.required;
|
|
114
|
+
lines.push(` ${toSafeIdentifier(fkKey)}${isRequired ? "" : "?"}: ${fkType};`);
|
|
115
|
+
emittedKeys.add(fkKey);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
lines.push(` };`);
|
|
119
|
+
|
|
120
|
+
// ── Insert Type ──
|
|
121
|
+
lines.push(` Insert: {`);
|
|
122
|
+
emittedKeys.clear();
|
|
123
|
+
|
|
124
|
+
for (const [key, rawProp] of Object.entries(properties)) {
|
|
125
|
+
const prop = rawProp as Property;
|
|
126
|
+
if (prop.type === "relation") continue;
|
|
127
|
+
const tsType = propertyToTypeScriptType(prop);
|
|
128
|
+
const isRequired = prop.validation?.required;
|
|
129
|
+
const typedProp = prop as StringProperty | NumberProperty;
|
|
130
|
+
const isAutoId = "isId" in prop && typedProp.isId && typedProp.isId !== "manual" && typedProp.isId !== true;
|
|
131
|
+
const isOptional = !isRequired || isAutoId;
|
|
132
|
+
lines.push(` ${toSafeIdentifier(key)}${isOptional ? "?" : ""}: ${tsType};`);
|
|
133
|
+
emittedKeys.add(key);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
for (const [relKey, relation] of Object.entries(resolvedRelations)) {
|
|
137
|
+
if (relation.direction === "owning" && relation.cardinality === "one" && relation.localKey) {
|
|
138
|
+
const fkKey = relation.localKey;
|
|
139
|
+
if (emittedKeys.has(fkKey)) continue;
|
|
140
|
+
let fkType = "string | number";
|
|
141
|
+
// simple fallback
|
|
142
|
+
const isRequired = relation.validation?.required;
|
|
143
|
+
lines.push(` ${toSafeIdentifier(fkKey)}${isRequired ? "" : "?"}: ${fkType};`);
|
|
144
|
+
emittedKeys.add(fkKey);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
lines.push(` };`);
|
|
148
|
+
|
|
149
|
+
// ── Update Type ──
|
|
150
|
+
lines.push(` Update: {`);
|
|
151
|
+
emittedKeys.clear();
|
|
152
|
+
for (const [key, rawProp] of Object.entries(properties)) {
|
|
153
|
+
const prop = rawProp as Property;
|
|
154
|
+
if (prop.type === "relation") continue;
|
|
155
|
+
const tsType = propertyToTypeScriptType(prop);
|
|
156
|
+
lines.push(` ${toSafeIdentifier(key)}?: ${tsType};`);
|
|
157
|
+
emittedKeys.add(key);
|
|
158
|
+
}
|
|
159
|
+
for (const [relKey, relation] of Object.entries(resolvedRelations)) {
|
|
160
|
+
if (relation.direction === "owning" && relation.cardinality === "one" && relation.localKey) {
|
|
161
|
+
const fkKey = relation.localKey;
|
|
162
|
+
if (emittedKeys.has(fkKey)) continue;
|
|
163
|
+
lines.push(` ${toSafeIdentifier(fkKey)}?: string | number;`);
|
|
164
|
+
emittedKeys.add(fkKey);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
lines.push(` };`);
|
|
168
|
+
|
|
169
|
+
lines.push(` };`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
lines.push(`}`);
|
|
173
|
+
lines.push(``);
|
|
174
|
+
|
|
175
|
+
return lines.join("\n");
|
|
176
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @rebasepro/sdk-generator
|
|
3
|
+
*
|
|
4
|
+
* Generates a purely typed Typescript database definition.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { EntityCollection } from "@rebasepro/types";
|
|
8
|
+
import { generateTypedefs } from "./generate-types";
|
|
9
|
+
|
|
10
|
+
export { generateTypedefs } from "./generate-types";
|
|
11
|
+
export { toPascalCase, toCamelCase, toSafeIdentifier, indent } from "./utils";
|
|
12
|
+
|
|
13
|
+
// ─── Public API ────────────────────────────────────────────────────
|
|
14
|
+
|
|
15
|
+
export interface GeneratedFile {
|
|
16
|
+
/** Relative file path within the output directory */
|
|
17
|
+
path: string;
|
|
18
|
+
/** File content */
|
|
19
|
+
content: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface GenerateSDKOptions {
|
|
23
|
+
/** Whether to include a README file (default: true) */
|
|
24
|
+
includeReadme?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function generateSDK(
|
|
28
|
+
collections: EntityCollection[],
|
|
29
|
+
options: GenerateSDKOptions = {},
|
|
30
|
+
): GeneratedFile[] {
|
|
31
|
+
const files: GeneratedFile[] = [];
|
|
32
|
+
|
|
33
|
+
files.push({
|
|
34
|
+
path: "database.types.ts",
|
|
35
|
+
content: generateTypedefs(collections)
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (options.includeReadme !== false) {
|
|
39
|
+
files.push({
|
|
40
|
+
path: "README.md",
|
|
41
|
+
content: `# Rebase SDK
|
|
42
|
+
|
|
43
|
+
> Auto-generated by \`rebase generate-sdk\`. Do not edit manually.
|
|
44
|
+
|
|
45
|
+
## Usage
|
|
46
|
+
|
|
47
|
+
1. Install the client package:
|
|
48
|
+
\`\`\`bash
|
|
49
|
+
npm install @rebasepro/client
|
|
50
|
+
\`\`\`
|
|
51
|
+
|
|
52
|
+
2. Initialize with your generated types:
|
|
53
|
+
\`\`\`typescript
|
|
54
|
+
import { createRebaseClient } from '@rebasepro/client';
|
|
55
|
+
import type { Database } from './database.types';
|
|
56
|
+
|
|
57
|
+
const rebase = createRebaseClient<Database>({
|
|
58
|
+
baseUrl: 'http://localhost:3001',
|
|
59
|
+
// token: '...', // User token if not using auth module
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Both syntax styles are fully typed!
|
|
63
|
+
const { data: users } = await rebase.data.users.find();
|
|
64
|
+
const { data: posts } = await rebase.data.collection('posts').find();
|
|
65
|
+
\`\`\`
|
|
66
|
+
`
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return files;
|
|
71
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility functions for the SDK generator
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Convert a slug/snake_case string to PascalCase
|
|
7
|
+
* e.g. "private_notes" → "PrivateNotes"
|
|
8
|
+
*/
|
|
9
|
+
export function toPascalCase(str: string): string {
|
|
10
|
+
return str
|
|
11
|
+
.split(/[_\-\s]+/)
|
|
12
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
13
|
+
.join("");
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Convert a slug/snake_case string to camelCase
|
|
18
|
+
* e.g. "private_notes" → "privateNotes"
|
|
19
|
+
*/
|
|
20
|
+
export function toCamelCase(str: string): string {
|
|
21
|
+
const pascal = toPascalCase(str);
|
|
22
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Convert a slug to a safe JS identifier
|
|
27
|
+
* e.g. "private-notes" → "privateNotes"
|
|
28
|
+
*/
|
|
29
|
+
export function toSafeIdentifier(str: string): string {
|
|
30
|
+
return toCamelCase(str.replace(/[^a-zA-Z0-9_]/g, "_"));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Indent a block of text by a given number of spaces
|
|
35
|
+
*/
|
|
36
|
+
export function indent(text: string, spaces: number): string {
|
|
37
|
+
const pad = " ".repeat(spaces);
|
|
38
|
+
return text
|
|
39
|
+
.split("\n")
|
|
40
|
+
.map(line => (line.trim() ? pad + line : line))
|
|
41
|
+
.join("\n");
|
|
42
|
+
}
|