@rebasepro/sdk-generator 0.2.1 → 0.2.4

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.
Files changed (32) hide show
  1. package/dist/common/src/collections/default-collections.d.ts +12 -0
  2. package/dist/common/src/collections/index.d.ts +1 -0
  3. package/dist/common/src/data/query_builder.d.ts +51 -0
  4. package/dist/common/src/index.d.ts +1 -0
  5. package/dist/common/src/util/permissions.d.ts +1 -0
  6. package/dist/index.cjs +3 -0
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.es.js +3 -0
  9. package/dist/index.es.js.map +1 -1
  10. package/dist/types/src/controllers/auth.d.ts +2 -24
  11. package/dist/types/src/controllers/client.d.ts +0 -3
  12. package/dist/types/src/controllers/collection_registry.d.ts +1 -1
  13. package/dist/types/src/controllers/data.d.ts +21 -0
  14. package/dist/types/src/controllers/data_driver.d.ts +18 -0
  15. package/dist/types/src/controllers/registry.d.ts +5 -4
  16. package/dist/types/src/rebase_context.d.ts +1 -1
  17. package/dist/types/src/types/auth_adapter.d.ts +2 -4
  18. package/dist/types/src/types/collections.d.ts +0 -4
  19. package/dist/types/src/types/component_ref.d.ts +1 -1
  20. package/dist/types/src/types/cron.d.ts +1 -1
  21. package/dist/types/src/types/entity_views.d.ts +1 -0
  22. package/dist/types/src/types/export_import.d.ts +1 -1
  23. package/dist/types/src/types/formex.d.ts +2 -2
  24. package/dist/types/src/types/properties.d.ts +2 -2
  25. package/dist/types/src/types/translations.d.ts +28 -12
  26. package/dist/types/src/types/user_management_delegate.d.ts +6 -4
  27. package/dist/types/src/users/roles.d.ts +0 -8
  28. package/package.json +11 -6
  29. package/src/generate-types.ts +5 -5
  30. package/src/utils.ts +3 -0
  31. package/test/sdk-generator.test.ts +456 -40
  32. package/src/json-logic-js.d.ts +0 -8
@@ -12,15 +12,9 @@ const authorsCollection = {
12
12
  slug: "authors",
13
13
  table: "authors",
14
14
  properties: {
15
- id: { name: "ID",
16
- type: "number",
17
- isId: "increment",
18
- validation: { required: true } },
19
- name: { name: "Name",
20
- type: "string",
21
- validation: { required: true } },
22
- email: { name: "Email",
23
- type: "string" }
15
+ id: { name: "ID", type: "number", isId: "increment", validation: { required: true } },
16
+ name: { name: "Name", type: "string", validation: { required: true } },
17
+ email: { name: "Email", type: "string" }
24
18
  }
25
19
  } as unknown as EntityCollection;
26
20
 
@@ -30,7 +24,16 @@ describe("Utils", () => {
30
24
  expect(toPascalCase("private_notes")).toBe("PrivateNotes");
31
25
  });
32
26
  it("handles already PascalCase input", () => {
33
- expect(toPascalCase("TestEntities")).toBe("Testentities");
27
+ expect(toPascalCase("TestEntities")).toBe("Testentities"); // Note: toPascalCase implementation lowercases follow-up chars per word split
28
+ });
29
+ it("handles kebab-case", () => {
30
+ expect(toPascalCase("private-notes")).toBe("PrivateNotes");
31
+ });
32
+ it("handles space separated strings", () => {
33
+ expect(toPascalCase("private notes")).toBe("PrivateNotes");
34
+ });
35
+ it("handles multiple delimiters and empty chunks", () => {
36
+ expect(toPascalCase("private--notes__here")).toBe("PrivateNotesHere");
34
37
  });
35
38
  });
36
39
 
@@ -38,51 +41,464 @@ describe("Utils", () => {
38
41
  it("converts snake_case to camelCase", () => {
39
42
  expect(toCamelCase("private_notes")).toBe("privateNotes");
40
43
  });
44
+ it("converts kebab-case to camelCase", () => {
45
+ expect(toCamelCase("private-notes")).toBe("privateNotes");
46
+ });
47
+ it("preserves already camelCase and PascalCase when no separators are present", () => {
48
+ expect(toCamelCase("customDomain")).toBe("customDomain");
49
+ expect(toCamelCase("GitRepoUrl")).toBe("gitRepoUrl");
50
+ });
41
51
  });
42
52
 
43
53
  describe("toSafeIdentifier", () => {
44
54
  it("converts slugs to camelCase", () => {
45
55
  expect(toSafeIdentifier("private-notes")).toBe("privateNotes");
46
56
  });
57
+ it("strips invalid JS identifier characters and camel cases", () => {
58
+ expect(toSafeIdentifier("my-special-slug!")).toBe("mySpecialSlug");
59
+ expect(toSafeIdentifier("some@name#here")).toBe("someNameHere");
60
+ });
61
+ });
62
+
63
+ describe("indent", () => {
64
+ it("indents multi-line string by given space count", () => {
65
+ const block = "line1\nline2\nline3";
66
+ const expected = " line1\n line2\n line3";
67
+ expect(indent(block, 2)).toBe(expected);
68
+ });
69
+ it("does not indent empty/whitespace lines", () => {
70
+ const block = "line1\n\n \nline2";
71
+ const expected = " line1\n\n \n line2";
72
+ expect(indent(block, 2)).toBe(expected);
73
+ });
47
74
  });
48
75
  });
49
76
 
50
- describe("generateTypedefs", () => {
51
- it("generates a typescript interface for a collection", () => {
52
- const ts = generateTypedefs([authorsCollection]);
77
+ describe("propertyToTypeScriptType mapping", () => {
78
+ it("maps basic types correctly", () => {
79
+ const col = {
80
+ slug: "types_collection",
81
+ properties: {
82
+ bool: { type: "boolean" },
83
+ dt: { type: "date" },
84
+ geo: { type: "geopoint" },
85
+ ref: { type: "reference" },
86
+ vec: { type: "vector" },
87
+ bin: { type: "binary" },
88
+ unknown: { type: "something-weird" }
89
+ }
90
+ } as unknown as EntityCollection;
91
+
92
+ const ts = generateTypedefs([col]);
93
+ expect(ts).toContain("bool?: boolean;");
94
+ expect(ts).toContain("dt?: string;");
95
+ expect(ts).toContain("geo?: { latitude: number; longitude: number; };");
96
+ expect(ts).toContain("ref?: string | number;");
97
+ expect(ts).toContain("vec?: number[];");
98
+ expect(ts).toContain("bin?: string;");
99
+ expect(ts).toContain("unknown?: any;");
100
+ });
53
101
 
54
- expect(ts).toContain("export interface Database {");
55
- expect(ts).toContain("authors: {");
102
+ describe("string enum mapping", () => {
103
+ it("maps string array enum", () => {
104
+ const col = {
105
+ slug: "posts",
106
+ properties: {
107
+ status: {
108
+ type: "string",
109
+ enum: ["draft", "published"]
110
+ }
111
+ }
112
+ } as unknown as EntityCollection;
56
113
 
57
- // Row Type
114
+ const ts = generateTypedefs([col]);
115
+ expect(ts).toContain('status?: "draft" | "published";');
116
+ });
117
+
118
+ it("maps string array of objects enum", () => {
119
+ const col = {
120
+ slug: "posts",
121
+ properties: {
122
+ status: {
123
+ type: "string",
124
+ enum: [
125
+ { id: "draft", label: "Draft" },
126
+ { id: "published", label: "Published" }
127
+ ]
128
+ }
129
+ }
130
+ } as unknown as EntityCollection;
131
+
132
+ const ts = generateTypedefs([col]);
133
+ expect(ts).toContain('status?: "draft" | "published";');
134
+ });
135
+
136
+ it("maps string record enum using keys", () => {
137
+ const col = {
138
+ slug: "posts",
139
+ properties: {
140
+ status: {
141
+ type: "string",
142
+ enum: {
143
+ draft: "Draft",
144
+ published: "Published"
145
+ }
146
+ }
147
+ }
148
+ } as unknown as EntityCollection;
149
+
150
+ const ts = generateTypedefs([col]);
151
+ expect(ts).toContain('status?: "draft" | "published";');
152
+ });
153
+ });
154
+
155
+ describe("number enum mapping", () => {
156
+ it("maps number array enum", () => {
157
+ const col = {
158
+ slug: "posts",
159
+ properties: {
160
+ level: {
161
+ type: "number",
162
+ enum: [1, 2, 3]
163
+ }
164
+ }
165
+ } as unknown as EntityCollection;
166
+
167
+ const ts = generateTypedefs([col]);
168
+ expect(ts).toContain("level?: 1 | 2 | 3;");
169
+ });
170
+
171
+ it("maps number array of objects enum", () => {
172
+ const col = {
173
+ slug: "posts",
174
+ properties: {
175
+ level: {
176
+ type: "number",
177
+ enum: [
178
+ { id: 10, label: "Low" },
179
+ { id: 20, label: "High" }
180
+ ]
181
+ }
182
+ }
183
+ } as unknown as EntityCollection;
184
+
185
+ const ts = generateTypedefs([col]);
186
+ expect(ts).toContain("level?: 10 | 20;");
187
+ });
188
+
189
+ it("maps number record enum using keys", () => {
190
+ const col = {
191
+ slug: "posts",
192
+ properties: {
193
+ level: {
194
+ type: "number",
195
+ enum: {
196
+ 1: "Low",
197
+ 2: "High"
198
+ }
199
+ }
200
+ }
201
+ } as unknown as EntityCollection;
202
+
203
+ const ts = generateTypedefs([col]);
204
+ expect(ts).toContain("level?: 1 | 2;");
205
+ });
206
+ });
207
+
208
+ describe("map property mapping", () => {
209
+ it("maps nested properties recursively", () => {
210
+ const col = {
211
+ slug: "users",
212
+ properties: {
213
+ profile: {
214
+ type: "map",
215
+ properties: {
216
+ age: { type: "number" },
217
+ tagline: { type: "string" }
218
+ }
219
+ }
220
+ }
221
+ } as unknown as EntityCollection;
222
+
223
+ const ts = generateTypedefs([col]);
224
+ expect(ts).toContain("profile?: { age: number; tagline: string; };");
225
+ });
226
+
227
+ it("falls back to Record<string, any> if properties is absent", () => {
228
+ const col = {
229
+ slug: "users",
230
+ properties: {
231
+ metadata: {
232
+ type: "map"
233
+ }
234
+ }
235
+ } as unknown as EntityCollection;
236
+
237
+ const ts = generateTypedefs([col]);
238
+ expect(ts).toContain("metadata?: Record<string, any>;");
239
+ });
240
+ });
241
+
242
+ describe("array property mapping", () => {
243
+ it("maps typed array using the 'of' property", () => {
244
+ const col = {
245
+ slug: "articles",
246
+ properties: {
247
+ tags: {
248
+ type: "array",
249
+ of: { type: "string" }
250
+ }
251
+ }
252
+ } as unknown as EntityCollection;
253
+
254
+ const ts = generateTypedefs([col]);
255
+ expect(ts).toContain("tags?: Array<string>;");
256
+ });
257
+
258
+ it("falls back to Array<any> if 'of' property is absent", () => {
259
+ const col = {
260
+ slug: "articles",
261
+ properties: {
262
+ generic: {
263
+ type: "array"
264
+ }
265
+ }
266
+ } as unknown as EntityCollection;
267
+
268
+ const ts = generateTypedefs([col]);
269
+ expect(ts).toContain("generic?: Array<any>;");
270
+ });
271
+ });
272
+ });
273
+
274
+ describe("generateTypedefs schemas configurations", () => {
275
+ describe("Insert type requirements", () => {
276
+ it("makes properties optional in Insert if validation required is false", () => {
277
+ const col = {
278
+ slug: "books",
279
+ properties: {
280
+ title: { type: "string" }
281
+ }
282
+ } as unknown as EntityCollection;
283
+
284
+ const ts = generateTypedefs([col]);
285
+ expect(ts).toContain("Insert: {");
286
+ expect(ts).toContain("title?: string;");
287
+ });
288
+
289
+ it("makes properties required in Insert if validation required is true", () => {
290
+ const col = {
291
+ slug: "books",
292
+ properties: {
293
+ title: { type: "string", validation: { required: true } }
294
+ }
295
+ } as unknown as EntityCollection;
296
+
297
+ const ts = generateTypedefs([col]);
298
+ expect(ts).toContain("Insert: {");
299
+ expect(ts).toContain("title: string;");
300
+ });
301
+
302
+ it("makes isId: 'increment' key optional in Insert even if validation required is true", () => {
303
+ const col = {
304
+ slug: "books",
305
+ properties: {
306
+ id: { type: "number", isId: "increment", validation: { required: true } }
307
+ }
308
+ } as unknown as EntityCollection;
309
+
310
+ const ts = generateTypedefs([col]);
311
+ expect(ts).toContain("Insert: {");
312
+ expect(ts).toContain("id?: number;");
313
+ });
314
+
315
+ it("makes isId: 'uuid' key optional in Insert even if validation required is true", () => {
316
+ const col = {
317
+ slug: "books",
318
+ properties: {
319
+ id: { type: "string", isId: "uuid", validation: { required: true } }
320
+ }
321
+ } as unknown as EntityCollection;
322
+
323
+ const ts = generateTypedefs([col]);
324
+ expect(ts).toContain("Insert: {");
325
+ expect(ts).toContain("id?: string;");
326
+ });
327
+
328
+ it("keeps isId: 'manual' key required in Insert if validation required is true", () => {
329
+ const col = {
330
+ slug: "books",
331
+ properties: {
332
+ id: { type: "string", isId: "manual", validation: { required: true } }
333
+ }
334
+ } as unknown as EntityCollection;
335
+
336
+ const ts = generateTypedefs([col]);
337
+ expect(ts).toContain("Insert: {");
338
+ expect(ts).toContain("id: string;");
339
+ });
340
+
341
+ it("keeps isId: true key required in Insert if validation required is true", () => {
342
+ const col = {
343
+ slug: "books",
344
+ properties: {
345
+ id: { type: "string", isId: true, validation: { required: true } }
346
+ }
347
+ } as unknown as EntityCollection;
348
+
349
+ const ts = generateTypedefs([col]);
350
+ expect(ts).toContain("Insert: {");
351
+ expect(ts).toContain("id: string;");
352
+ });
353
+ });
354
+
355
+ describe("Update type optionality", () => {
356
+ it("makes all direct fields optional in Update", () => {
357
+ const col = {
358
+ slug: "books",
359
+ properties: {
360
+ id: { type: "number", isId: "increment", validation: { required: true } },
361
+ title: { type: "string", validation: { required: true } }
362
+ }
363
+ } as unknown as EntityCollection;
364
+
365
+ const ts = generateTypedefs([col]);
366
+ expect(ts).toContain("Update: {");
367
+ expect(ts).toContain("id?: number;");
368
+ expect(ts).toContain("title?: string;");
369
+ });
370
+ });
371
+ });
372
+
373
+ describe("Collection relations and FK resolutions", () => {
374
+ it("handles target primary key type resolution for FK columns", () => {
375
+ const authorsCol = {
376
+ slug: "authors",
377
+ driver: "postgres",
378
+ properties: {
379
+ id: { type: "number", isId: "increment" }
380
+ }
381
+ } as unknown as EntityCollection;
382
+
383
+ const postsCol = {
384
+ slug: "posts",
385
+ driver: "postgres",
386
+ properties: {
387
+ id: { type: "number", isId: "increment" },
388
+ author: {
389
+ type: "relation",
390
+ target: () => authorsCol,
391
+ cardinality: "one",
392
+ direction: "owning",
393
+ localKey: "author_id"
394
+ }
395
+ }
396
+ } as unknown as EntityCollection;
397
+
398
+ const ts = generateTypedefs([postsCol, authorsCol]);
399
+
400
+ // Row should have authorId as number since authors PK (id) is number
401
+ expect(ts).toContain("posts: {");
402
+ expect(ts).toContain("Row: {");
403
+ expect(ts).toContain("authorId?: number;");
404
+ });
405
+
406
+ it("defaults to string | number when target primary key cannot be resolved", () => {
407
+ const postsCol = {
408
+ slug: "posts",
409
+ driver: "postgres",
410
+ properties: {
411
+ id: { type: "number", isId: "increment" },
412
+ author: {
413
+ type: "relation",
414
+ target: () => ({ name: "no-properties" }),
415
+ cardinality: "one",
416
+ direction: "owning",
417
+ localKey: "author_id"
418
+ }
419
+ }
420
+ } as unknown as EntityCollection;
421
+
422
+ const ts = generateTypedefs([postsCol]);
423
+
424
+ expect(ts).toContain("posts: {");
58
425
  expect(ts).toContain("Row: {");
59
- expect(ts).toContain("id: number;");
60
- expect(ts).toContain("name: string;");
61
- expect(ts).toContain("email?: string;");
62
-
63
- // Insert Type
64
- expect(ts).toContain("Insert: {");
65
- expect(ts).toContain("id?: number;");
66
- expect(ts).toContain("name: string;");
67
- expect(ts).toContain("email?: string;");
68
-
69
- // Update Type
70
- expect(ts).toContain("Update: {");
71
- expect(ts).toContain("id?: number;");
72
- expect(ts).toContain("name?: string;");
73
- expect(ts).toContain("email?: string;");
74
-
75
- // Dictionary
76
- expect(ts).toContain("export const collectionsDictionary = {");
77
- expect(ts).toContain("authors: \"authors\",");
426
+ expect(ts).toContain("authorId?: string | number;");
427
+ });
428
+
429
+ it("supports relation validation constraints making FK required", () => {
430
+ const authorsCol = {
431
+ slug: "authors",
432
+ driver: "postgres",
433
+ properties: {
434
+ id: { type: "string", isId: "uuid" }
435
+ }
436
+ } as unknown as EntityCollection;
437
+
438
+ const postsCol = {
439
+ slug: "posts",
440
+ driver: "postgres",
441
+ properties: {
442
+ id: { type: "number", isId: "increment" },
443
+ author: {
444
+ type: "relation",
445
+ relationName: "author_rel",
446
+ collectionPath: "authors"
447
+ }
448
+ },
449
+ relations: [
450
+ {
451
+ relationName: "author_rel",
452
+ target: () => authorsCol,
453
+ cardinality: "one",
454
+ direction: "owning",
455
+ localKey: "author_id",
456
+ validation: { required: true }
457
+ }
458
+ ]
459
+ } as unknown as EntityCollection;
460
+
461
+ const ts = generateTypedefs([postsCol, authorsCol]);
462
+
463
+ expect(ts).toContain("posts: {");
464
+ expect(ts).toContain("Row: {");
465
+ expect(ts).toContain("authorId: string;"); // target is string uuid, validation required is true
466
+ });
467
+
468
+ it("generates relation fields mapped correctly to relation type helpers", () => {
469
+ const tagsCol = {
470
+ slug: "tags",
471
+ driver: "postgres",
472
+ properties: {
473
+ id: { type: "number", isId: "increment" }
474
+ }
475
+ } as unknown as EntityCollection;
476
+
477
+ const postsCol = {
478
+ slug: "posts",
479
+ driver: "postgres",
480
+ properties: {
481
+ id: { type: "number", isId: "increment" },
482
+ tags: {
483
+ type: "relation",
484
+ target: () => tagsCol,
485
+ cardinality: "many",
486
+ direction: "owning"
487
+ }
488
+ }
489
+ } as unknown as EntityCollection;
490
+
491
+ const ts = generateTypedefs([postsCol, tagsCol]);
492
+
493
+ // Row should have relation helper field
494
+ expect(ts).toContain("tags?: Array<{ id: string | number; path: string; __type: \"relation\"; data?: any }>;");
78
495
  });
79
496
  });
80
497
 
81
- describe("generateSDK", () => {
82
- it("returns an array of generated files including database.types.ts and README.md", () => {
83
- const files = generateSDK([authorsCollection]);
84
- expect(files.length).toBe(2);
498
+ describe("generateSDK configurations", () => {
499
+ it("does not generate README.md if includeReadme is false", () => {
500
+ const files = generateSDK([authorsCollection], { includeReadme: false });
501
+ expect(files.length).toBe(1);
85
502
  expect(files[0].path).toBe("database.types.ts");
86
- expect(files[1].path).toBe("README.md");
87
503
  });
88
504
  });
@@ -1,8 +0,0 @@
1
- declare module "json-logic-js" {
2
- interface JsonLogic {
3
- apply(logic: any, data?: any): any;
4
- add_operation(name: string, fn: (...args: any[]) => any): void;
5
- }
6
- const jsonLogic: JsonLogic;
7
- export default jsonLogic;
8
- }