Skip to content

Commit 5758e4c

Browse files
authored
Merge pull request #58 from powersync-ja/tablev2-schema-generator
Add TableV2 / TypeScript schema generator
2 parents af369ae + 756ad8c commit 5758e4c

File tree

6 files changed

+161
-10
lines changed

6 files changed

+161
-10
lines changed

.changeset/thirty-bikes-move.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/service-sync-rules': patch
3+
---
4+
5+
Add TypeScript/TableV2 schema generator

packages/sync-rules/src/JsSchemaGenerator.ts renamed to packages/sync-rules/src/JsLegacySchemaGenerator.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ import { SchemaGenerator } from './SchemaGenerator.js';
33
import { SqlSyncRules } from './SqlSyncRules.js';
44
import { SourceSchema } from './types.js';
55

6-
export class JsSchemaGenerator extends SchemaGenerator {
7-
readonly key = 'js';
8-
readonly label = 'JavaScript';
9-
readonly mediaType = 'application/javascript';
6+
export class JsLegacySchemaGenerator extends SchemaGenerator {
7+
readonly key = 'jsLegacy';
8+
readonly label = 'JavaScript (legacy syntax)';
9+
readonly mediaType = 'text/javascript';
1010
readonly fileName = 'schema.js';
1111

1212
generate(source: SqlSyncRules, schema: SourceSchema): string {
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
import { ColumnDefinition, TYPE_INTEGER, TYPE_REAL, TYPE_TEXT } from './ExpressionType.js';
2+
import { SchemaGenerator } from './SchemaGenerator.js';
3+
import { SqlSyncRules } from './SqlSyncRules.js';
4+
import { SourceSchema } from './types.js';
5+
6+
export interface TsSchemaGeneratorOptions {
7+
language?: TsSchemaLanguage;
8+
imports?: TsSchemaImports;
9+
}
10+
11+
export enum TsSchemaLanguage {
12+
ts = 'ts',
13+
/** Excludes types from the generated schema. */
14+
js = 'js'
15+
}
16+
17+
export enum TsSchemaImports {
18+
web = 'web',
19+
reactNative = 'reactNative',
20+
/**
21+
* Emits imports for `@powersync/web`, with comments for `@powersync/react-native`.
22+
*/
23+
auto = 'auto'
24+
}
25+
26+
export class TsSchemaGenerator extends SchemaGenerator {
27+
readonly key: string;
28+
readonly fileName: string;
29+
readonly mediaType: string;
30+
readonly label: string;
31+
32+
readonly language: TsSchemaLanguage;
33+
34+
constructor(public readonly options: TsSchemaGeneratorOptions = {}) {
35+
super();
36+
37+
this.language = options.language ?? TsSchemaLanguage.ts;
38+
this.key = this.language;
39+
if (this.language == TsSchemaLanguage.ts) {
40+
this.fileName = 'schema.ts';
41+
this.mediaType = 'text/typescript';
42+
this.label = 'TypeScript';
43+
} else {
44+
this.fileName = 'schema.js';
45+
this.mediaType = 'text/javascript';
46+
this.label = 'JavaScript';
47+
}
48+
}
49+
50+
generate(source: SqlSyncRules, schema: SourceSchema): string {
51+
const tables = super.getAllTables(source, schema);
52+
53+
return `${this.generateImports()}
54+
55+
${tables.map((table) => this.generateTable(table.name, table.columns)).join('\n\n')}
56+
57+
export const AppSchema = new Schema({
58+
${tables.map((table) => table.name).join(',\n ')}
59+
});
60+
61+
${this.generateTypeExports()}`;
62+
}
63+
64+
private generateTypeExports() {
65+
if (this.language == TsSchemaLanguage.ts) {
66+
return `export type Database = (typeof AppSchema)['types'];\n`;
67+
} else {
68+
return ``;
69+
}
70+
}
71+
72+
private generateImports() {
73+
const importStyle = this.options.imports ?? 'auto';
74+
if (importStyle == TsSchemaImports.web) {
75+
return `import { column, Schema, TableV2 } from '@powersync/web';`;
76+
} else if (importStyle == TsSchemaImports.reactNative) {
77+
return `import { column, Schema, TableV2 } from '@powersync/react-native';`;
78+
} else {
79+
return `import { column, Schema, TableV2 } from '@powersync/web';
80+
// OR: import { column, Schema, TableV2 } from '@powersync/react-native';`;
81+
}
82+
}
83+
84+
private generateTable(name: string, columns: ColumnDefinition[]): string {
85+
return `const ${name} = new TableV2(
86+
{
87+
// id column (text) is automatically included
88+
${columns.map((c) => this.generateColumn(c)).join(',\n ')}
89+
},
90+
{ indexes: {} }
91+
);`;
92+
}
93+
94+
private generateColumn(column: ColumnDefinition) {
95+
const t = column.type;
96+
if (t.typeFlags & TYPE_TEXT) {
97+
return `${column.name}: column.text`;
98+
} else if (t.typeFlags & TYPE_REAL) {
99+
return `${column.name}: column.real`;
100+
} else if (t.typeFlags & TYPE_INTEGER) {
101+
return `${column.name}: column.integer`;
102+
} else {
103+
return `${column.name}: column.text`;
104+
}
105+
}
106+
}
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import { DartSchemaGenerator } from './DartSchemaGenerator.js';
2-
import { JsSchemaGenerator } from './JsSchemaGenerator.js';
2+
import { JsLegacySchemaGenerator } from './JsLegacySchemaGenerator.js';
3+
import { TsSchemaGenerator, TsSchemaLanguage } from './TsSchemaGenerator.js';
34

45
export const schemaGenerators = {
5-
js: new JsSchemaGenerator(),
6+
ts: new TsSchemaGenerator(),
7+
js: new TsSchemaGenerator({ language: TsSchemaLanguage.js }),
8+
jsLegacy: new JsLegacySchemaGenerator(),
69
dart: new DartSchemaGenerator()
710
};

packages/sync-rules/src/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ export * from './StaticSchema.js';
1313
export * from './ExpressionType.js';
1414
export * from './SchemaGenerator.js';
1515
export * from './DartSchemaGenerator.js';
16-
export * from './JsSchemaGenerator.js';
16+
export * from './JsLegacySchemaGenerator.js';
17+
export * from './TsSchemaGenerator.js';
1718
export * from './generators.js';
1819
export * from './SqlDataQuery.js';
1920
export * from './request_functions.js';

packages/sync-rules/test/src/sync_rules.test.ts

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import {
33
DEFAULT_SCHEMA,
44
DEFAULT_TAG,
55
DartSchemaGenerator,
6-
JsSchemaGenerator,
6+
JsLegacySchemaGenerator,
77
SqlSyncRules,
8-
StaticSchema
8+
StaticSchema,
9+
TsSchemaGenerator
910
} from '../../src/index.js';
11+
1012
import { ASSETS, BASIC_SCHEMA, TestSourceTable, USERS, normalizeTokenParameters } from './util.js';
1113

1214
describe('sync rules', () => {
@@ -761,7 +763,7 @@ bucket_definitions:
761763
]);
762764
`);
763765

764-
expect(new JsSchemaGenerator().generate(rules, schema)).toEqual(`new Schema([
766+
expect(new JsLegacySchemaGenerator().generate(rules, schema)).toEqual(`new Schema([
765767
new Table({
766768
name: 'assets1',
767769
columns: [
@@ -781,5 +783,39 @@ bucket_definitions:
781783
})
782784
])
783785
`);
786+
787+
expect(new TsSchemaGenerator().generate(rules, schema)).toEqual(
788+
`import { column, Schema, TableV2 } from '@powersync/web';
789+
// OR: import { column, Schema, TableV2 } from '@powersync/react-native';
790+
791+
const assets1 = new TableV2(
792+
{
793+
// id column (text) is automatically included
794+
name: column.text,
795+
count: column.integer,
796+
owner_id: column.text
797+
},
798+
{ indexes: {} }
799+
);
800+
801+
const assets2 = new TableV2(
802+
{
803+
// id column (text) is automatically included
804+
name: column.text,
805+
count: column.integer,
806+
other_id: column.text,
807+
foo: column.text
808+
},
809+
{ indexes: {} }
810+
);
811+
812+
export const AppSchema = new Schema({
813+
assets1,
814+
assets2
815+
});
816+
817+
export type Database = (typeof AppSchema)['types'];
818+
`
819+
);
784820
});
785821
});

0 commit comments

Comments
 (0)