Skip to content

Commit ac6ae0d

Browse files
SDK Schema Generators (#243)
1 parent 77fcc2f commit ac6ae0d

File tree

12 files changed

+384
-37
lines changed

12 files changed

+384
-37
lines changed

.changeset/beige-poems-share.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': minor
3+
---
4+
5+
Added Schema generators for Kotlin, Swift and DotNet

packages/sync-rules/src/index.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
export * from './BucketDescription.js';
2-
export * from './DartSchemaGenerator.js';
2+
export * from './BucketParameterQuerier.js';
33
export * from './errors.js';
44
export * from './events/SqlEventDescriptor.js';
55
export * from './events/SqlEventSourceQuery.js';
66
export * from './ExpressionType.js';
7-
export * from './generators.js';
87
export * from './IdSequence.js';
9-
export * from './JsLegacySchemaGenerator.js';
108
export * from './json_schema.js';
119
export * from './request_functions.js';
12-
export * from './SchemaGenerator.js';
10+
export * from './schema-generators/schema-generators.js';
1311
export * from './SourceTableInterface.js';
1412
export * from './sql_filters.js';
1513
export * from './sql_functions.js';
@@ -18,7 +16,5 @@ export * from './SqlParameterQuery.js';
1816
export * from './SqlSyncRules.js';
1917
export * from './StaticSchema.js';
2018
export * from './TablePattern.js';
21-
export * from './TsSchemaGenerator.js';
2219
export * from './types.js';
2320
export * from './utils.js';
24-
export * from './BucketParameterQuerier.js';

packages/sync-rules/src/DartSchemaGenerator.ts renamed to packages/sync-rules/src/schema-generators/DartSchemaGenerator.ts

Lines changed: 6 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { ColumnDefinition, ExpressionType, TYPE_INTEGER, TYPE_REAL, TYPE_TEXT } from './ExpressionType.js';
1+
import { ColumnDefinition, ExpressionType } from '../ExpressionType.js';
2+
import { SqlSyncRules } from '../SqlSyncRules.js';
3+
import { SourceSchema } from '../types.js';
24
import { GenerateSchemaOptions, SchemaGenerator } from './SchemaGenerator.js';
3-
import { SqlSyncRules } from './SqlSyncRules.js';
4-
import { SourceSchema } from './types.js';
55

66
export class DartSchemaGenerator extends SchemaGenerator {
77
readonly key = 'dart';
@@ -41,7 +41,7 @@ ${generated.join('\n')}
4141
}
4242

4343
private generateColumn(column: ColumnDefinition) {
44-
return `Column.${dartColumnType(column)}('${column.name}')`;
44+
return `Column.${this.columnType(column)}('${column.name}')`;
4545
}
4646
}
4747

@@ -97,28 +97,15 @@ export class DartFlutterFlowSchemaGenerator extends SchemaGenerator {
9797
view_name: null,
9898
local_only: localOnly,
9999
insert_only: false,
100-
columns: columns.map(this.generateColumn),
100+
columns: columns.map((c) => this.generateColumn(c)),
101101
indexes: []
102102
};
103103
}
104104

105105
private generateColumn(definition: ColumnDefinition): object {
106106
return {
107107
name: definition.name,
108-
type: dartColumnType(definition)
108+
type: this.columnType(definition)
109109
};
110110
}
111111
}
112-
113-
const dartColumnType = (def: ColumnDefinition) => {
114-
const t = def.type;
115-
if (t.typeFlags & TYPE_TEXT) {
116-
return 'text';
117-
} else if (t.typeFlags & TYPE_REAL) {
118-
return 'real';
119-
} else if (t.typeFlags & TYPE_INTEGER) {
120-
return 'integer';
121-
} else {
122-
return 'text';
123-
}
124-
};
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { ColumnDefinition, TYPE_INTEGER, TYPE_REAL, TYPE_TEXT } from '../ExpressionType.js';
2+
import { SqlSyncRules } from '../SqlSyncRules.js';
3+
import { SourceSchema } from '../types.js';
4+
import { GenerateSchemaOptions, SchemaGenerator } from './SchemaGenerator.js';
5+
6+
export class DotNetSchemaGenerator extends SchemaGenerator {
7+
readonly key = 'dotnet';
8+
readonly label = '.Net';
9+
readonly mediaType = 'text/x-csharp';
10+
readonly fileName = 'Schema.cs';
11+
12+
generate(source: SqlSyncRules, schema: SourceSchema, options?: GenerateSchemaOptions): string {
13+
const tables = super.getAllTables(source, schema);
14+
15+
return `using PowerSync.Common.DB.Schema;
16+
17+
class AppSchema
18+
{
19+
${tables.map((table) => this.generateTable(table.name, table.columns, options)).join('\n\n ')}
20+
21+
public static Schema PowerSyncSchema = new Schema(new Dictionary<string, Table>
22+
{
23+
${tables.map((table) => `{"${table.name}", ${this.toUpperCaseFirstLetter(table.name)}}`).join(',\n ')}
24+
});
25+
}`;
26+
}
27+
28+
private toUpperCaseFirstLetter(str: string): string {
29+
return str.charAt(0).toUpperCase() + str.slice(1);
30+
}
31+
32+
private generateTable(name: string, columns: ColumnDefinition[], options?: GenerateSchemaOptions): string {
33+
const generated = columns.map((c, i) => {
34+
const last = i === columns.length - 1;
35+
const base = this.generateColumn(c);
36+
let withFormatting: string;
37+
if (last) {
38+
withFormatting = ` ${base}`;
39+
} else {
40+
withFormatting = ` ${base},`;
41+
}
42+
43+
if (options?.includeTypeComments && c.originalType != null) {
44+
return `${withFormatting} // ${c.originalType}`;
45+
} else {
46+
return withFormatting;
47+
}
48+
});
49+
return `public static Table ${this.toUpperCaseFirstLetter(name)} = new Table(new Dictionary<string, ColumnType>
50+
{
51+
${generated.join('\n ')}
52+
});`;
53+
}
54+
55+
private generateColumn(column: ColumnDefinition): string {
56+
return `{ "${column.name}", ${cSharpColumnType(column)} }`;
57+
}
58+
}
59+
60+
const cSharpColumnType = (def: ColumnDefinition): string => {
61+
const t = def.type;
62+
if (t.typeFlags & TYPE_TEXT) {
63+
return 'ColumnType.TEXT';
64+
} else if (t.typeFlags & TYPE_REAL) {
65+
return 'ColumnType.REAL';
66+
} else if (t.typeFlags & TYPE_INTEGER) {
67+
return 'ColumnType.INTEGER';
68+
} else {
69+
return 'ColumnType.TEXT';
70+
}
71+
};

packages/sync-rules/src/JsLegacySchemaGenerator.ts renamed to packages/sync-rules/src/schema-generators/JsLegacySchemaGenerator.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { ColumnDefinition, TYPE_INTEGER, TYPE_REAL, TYPE_TEXT } from './ExpressionType.js';
1+
import { ColumnDefinition, TYPE_INTEGER, TYPE_REAL, TYPE_TEXT } from '../ExpressionType.js';
2+
import { SqlSyncRules } from '../SqlSyncRules.js';
3+
import { SourceSchema } from '../types.js';
24
import { SchemaGenerator } from './SchemaGenerator.js';
3-
import { SqlSyncRules } from './SqlSyncRules.js';
4-
import { SourceSchema } from './types.js';
55

66
export class JsLegacySchemaGenerator extends SchemaGenerator {
77
readonly key = 'jsLegacy';
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { ColumnDefinition } from '../ExpressionType.js';
2+
import { SqlSyncRules } from '../SqlSyncRules.js';
3+
import { SourceSchema } from '../types.js';
4+
import { GenerateSchemaOptions, SchemaGenerator } from './SchemaGenerator.js';
5+
6+
export class KotlinSchemaGenerator extends SchemaGenerator {
7+
readonly key = 'kotlin';
8+
readonly label = 'Kotlin';
9+
readonly mediaType = 'text/x-kotlin';
10+
readonly fileName = 'schema.kt';
11+
12+
generate(source: SqlSyncRules, schema: SourceSchema, options?: GenerateSchemaOptions): string {
13+
const tables = super.getAllTables(source, schema);
14+
15+
return `import com.powersync.db.schema.Column
16+
import com.powersync.db.schema.Schema
17+
import com.powersync.db.schema.Table
18+
19+
val schema = Schema(
20+
${tables.map((table) => this.generateTable(table.name, table.columns, options)).join(',\n ')}
21+
)`;
22+
}
23+
24+
private generateTable(name: string, columns: ColumnDefinition[], options?: GenerateSchemaOptions): string {
25+
const generated = columns.map((c, i) => {
26+
const last = i === columns.length - 1;
27+
const base = this.generateColumn(c);
28+
let withFormatting: string;
29+
if (last) {
30+
withFormatting = ` ${base}`;
31+
} else {
32+
withFormatting = ` ${base},`;
33+
}
34+
35+
if (options?.includeTypeComments && c.originalType != null) {
36+
return `${withFormatting} // ${c.originalType}`;
37+
} else {
38+
return withFormatting;
39+
}
40+
});
41+
return `Table(
42+
name = "${name}",
43+
columns = listOf(
44+
${generated.join('\n')}
45+
)
46+
)`;
47+
}
48+
49+
private generateColumn(column: ColumnDefinition): string {
50+
return `Column.${this.columnType(column)}("${column.name}")`;
51+
}
52+
}

packages/sync-rules/src/SchemaGenerator.ts renamed to packages/sync-rules/src/schema-generators/SchemaGenerator.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { ColumnDefinition } from './ExpressionType.js';
2-
import { SqlSyncRules } from './SqlSyncRules.js';
3-
import { SourceSchema } from './types.js';
1+
import { ColumnDefinition, TYPE_INTEGER, TYPE_REAL, TYPE_TEXT } from '../ExpressionType.js';
2+
import { SqlSyncRules } from '../SqlSyncRules.js';
3+
import { SourceSchema } from '../types.js';
44

55
export interface GenerateSchemaOptions {
66
includeTypeComments?: boolean;
@@ -38,4 +38,21 @@ export abstract class SchemaGenerator {
3838
abstract readonly fileName: string;
3939

4040
abstract generate(source: SqlSyncRules, schema: SourceSchema, options?: GenerateSchemaOptions): string;
41+
42+
/**
43+
* @param def The column definition to generate the type for.
44+
* @returns The SDK column type for the given column definition.
45+
*/
46+
columnType(def: ColumnDefinition): string {
47+
const { type } = def;
48+
if (type.typeFlags & TYPE_TEXT) {
49+
return 'text';
50+
} else if (type.typeFlags & TYPE_REAL) {
51+
return 'real';
52+
} else if (type.typeFlags & TYPE_INTEGER) {
53+
return 'integer';
54+
} else {
55+
return 'text';
56+
}
57+
}
4158
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import { ColumnDefinition } from '../ExpressionType.js';
2+
import { SqlSyncRules } from '../SqlSyncRules.js';
3+
import { SourceSchema } from '../types.js';
4+
import { GenerateSchemaOptions, SchemaGenerator } from './SchemaGenerator.js';
5+
6+
export class SwiftSchemaGenerator extends SchemaGenerator {
7+
readonly key = 'swift';
8+
readonly label = 'Swift';
9+
readonly mediaType = 'text/x-swift';
10+
readonly fileName = 'schema.swift';
11+
12+
generate(source: SqlSyncRules, schema: SourceSchema, options?: GenerateSchemaOptions): string {
13+
const tables = super.getAllTables(source, schema);
14+
15+
return `import PowerSync
16+
17+
let schema = Schema(
18+
${tables.map((table) => this.generateTable(table.name, table.columns, options)).join(',\n ')}
19+
)`;
20+
}
21+
22+
private generateTable(name: string, columns: ColumnDefinition[], options?: GenerateSchemaOptions): string {
23+
const generated = columns.map((c, i) => {
24+
const last = i === columns.length - 1;
25+
const base = this.generateColumn(c);
26+
let withFormatting: string;
27+
if (last) {
28+
withFormatting = ` ${base}`;
29+
} else {
30+
withFormatting = ` ${base},`;
31+
}
32+
33+
if (options?.includeTypeComments && c.originalType != null) {
34+
return `${withFormatting} // ${c.originalType}`;
35+
} else {
36+
return withFormatting;
37+
}
38+
});
39+
return `Table(
40+
name: "${name}",
41+
columns: [
42+
${generated.join('\n')}
43+
]
44+
)`;
45+
}
46+
47+
private generateColumn(column: ColumnDefinition): string {
48+
return `.${this.columnType(column)}("${column.name}")`;
49+
}
50+
}

packages/sync-rules/src/TsSchemaGenerator.ts renamed to packages/sync-rules/src/schema-generators/TsSchemaGenerator.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { ColumnDefinition, TYPE_INTEGER, TYPE_REAL, TYPE_TEXT } from './ExpressionType.js';
1+
import { ColumnDefinition, TYPE_INTEGER, TYPE_REAL, TYPE_TEXT } from '../ExpressionType.js';
2+
import { SqlSyncRules } from '../SqlSyncRules.js';
3+
import { SourceSchema } from '../types.js';
24
import { GenerateSchemaOptions, SchemaGenerator } from './SchemaGenerator.js';
3-
import { SqlSyncRules } from './SqlSyncRules.js';
4-
import { SourceSchema } from './types.js';
55

66
export interface TsSchemaGeneratorOptions {
77
language?: TsSchemaLanguage;
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,17 @@
11
import { DartFlutterFlowSchemaGenerator, DartSchemaGenerator } from './DartSchemaGenerator.js';
2+
import { DotNetSchemaGenerator } from './DotNetSchemaGenerator.js';
23
import { JsLegacySchemaGenerator } from './JsLegacySchemaGenerator.js';
4+
import { KotlinSchemaGenerator } from './KotlinSchemaGenerator.js';
5+
import { SwiftSchemaGenerator } from './SwiftSchemaGenerator.js';
36
import { TsSchemaGenerator, TsSchemaLanguage } from './TsSchemaGenerator.js';
47

58
export const schemaGenerators = {
6-
ts: new TsSchemaGenerator(),
9+
dart: new DartSchemaGenerator(),
10+
dotNet: new DotNetSchemaGenerator(),
11+
flutterFlow: new DartFlutterFlowSchemaGenerator(),
712
js: new TsSchemaGenerator({ language: TsSchemaLanguage.js }),
813
jsLegacy: new JsLegacySchemaGenerator(),
9-
dart: new DartSchemaGenerator(),
10-
flutterFlow: new DartFlutterFlowSchemaGenerator()
14+
kotlin: new KotlinSchemaGenerator(),
15+
swift: new SwiftSchemaGenerator(),
16+
ts: new TsSchemaGenerator()
1117
};

0 commit comments

Comments
 (0)