Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/lix-integration' into paraglide-…
Browse files Browse the repository at this point in the history
…prerelease
  • Loading branch information
LorisSigrist committed Aug 27, 2024
2 parents 084566a + 8a7809a commit 0a859f0
Show file tree
Hide file tree
Showing 49 changed files with 1,176 additions and 250 deletions.
4 changes: 4 additions & 0 deletions inlang/source-code/fink2/src/helper/queryHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@ import { Bundle, InlangDatabaseSchema, Message, Variant } from "@inlang/sdk2";
import { Kysely } from "kysely";
import { json } from "./toJSONRawBuilder.ts";

/**
* @deprecated json mapping happens automatically https://github.com/opral/monorepo/pull/3078
* use the query api directly.
*/
const queryHelper = {
bundle: {
/*
Expand Down
3 changes: 3 additions & 0 deletions inlang/source-code/fink2/src/helper/toJSONRawBuilder.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { RawBuilder, sql } from "kysely";

/**
* @deprecated json mapping happens automatically https://github.com/opral/monorepo/pull/3078
*/
export function json<T>(value: T): RawBuilder<T> {
// NOTE we cant use jsonb for now since kisley
// - couldn't find out how to return json instead of bytes in a selectFrom(...).select statment
Expand Down
1 change: 1 addition & 0 deletions inlang/source-code/sdk2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
},
"devDependencies": {
"@types/murmurhash3js": "^3.0.7",
"@types/uuid": "^10.0.0",
"@vitest/coverage-v8": "^2.0.5",
"memfs": "4.6.0",
"typescript": "^5.5.2",
Expand Down
5 changes: 5 additions & 0 deletions inlang/source-code/sdk2/src/bundle-id/bundle-id.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ export function generateBundleId() {
}`;
}

export function isBundleId(id: string): boolean {
// naive implementation (good enough for now)
return id.split("_").length === 4;
}

/**
* The function generated a stable bundle id based on the input value.
*
Expand Down
33 changes: 33 additions & 0 deletions inlang/source-code/sdk2/src/database/createSchema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { sql, type Kysely } from "kysely";
import type { SqliteDatabase } from "sqlite-wasm-kysely";

export async function createSchema(args: {
db: Kysely<any>;
sqlite: SqliteDatabase;
}) {
return sql`
CREATE TABLE bundle (
id TEXT PRIMARY KEY DEFAULT (bundle_id()),
alias TEXT NOT NULL
);
CREATE TABLE message (
id TEXT PRIMARY KEY DEFAULT (uuid_v4()),
bundle_id TEXT NOT NULL,
locale TEXT NOT NULL,
declarations TEXT NOT NULL,
selectors TEXT NOT NULL
);
CREATE TABLE variant (
id TEXT PRIMARY KEY DEFAULT (uuid_v4()),
message_id TEXT NOT NULL,
match TEXT NOT NULL,
pattern TEXT NOT NULL
);
CREATE INDEX idx_message_bundle_id ON message (bundle_id);
CREATE INDEX idx_variant_message_id ON variant (message_id);
`.execute(args.db);
}
85 changes: 85 additions & 0 deletions inlang/source-code/sdk2/src/database/initDb.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import { createInMemoryDatabase } from "sqlite-wasm-kysely";
import { test, expect } from "vitest";
import { initDb } from "./initDb.js";
import { isBundleId } from "../bundle-id/bundle-id.js";
import { validate } from "uuid";
import { createSchema } from "./createSchema.js";

test("bundle ids should have a default value", async () => {
const sqlite = await createInMemoryDatabase({
readOnly: false,
});
const db = initDb({ sqlite });
await createSchema({ db, sqlite });

const bundle = await db
.insertInto("bundle")
.values({
alias: {
mock: "mock",
},
})
.returningAll()
.executeTakeFirstOrThrow();

expect(isBundleId(bundle.id)).toBe(true);
});

test("message ids should default to uuid", async () => {
const sqlite = await createInMemoryDatabase({
readOnly: false,
});
const db = initDb({ sqlite });
await createSchema({ db, sqlite });

const message = await db
.insertInto("message")
.values({
bundleId: "mock",
locale: "en",
selectors: [],
declarations: [],
})
.returningAll()
.executeTakeFirstOrThrow();

expect(validate(message.id)).toBe(true);
});

test("variant ids should default to uuid", async () => {
const sqlite = await createInMemoryDatabase({
readOnly: false,
});
const db = initDb({ sqlite });
await createSchema({ db, sqlite });

const variant = await db
.insertInto("variant")
.values({
messageId: "mock",
match: {},
pattern: [],
})
.returningAll()
.executeTakeFirstOrThrow();

expect(validate(variant.id)).toBe(true);
});

test("it should handle json serialization", async () => {
const sqlite = await createInMemoryDatabase({
readOnly: false,
});
const db = initDb({ sqlite });
await createSchema({ db, sqlite });

const bundle = await db
.insertInto("bundle")
.values({
alias: { mock: "mock" },
})
.returningAll()
.executeTakeFirstOrThrow();

expect(bundle.alias).toEqual({ mock: "mock" });
});
34 changes: 34 additions & 0 deletions inlang/source-code/sdk2/src/database/initDb.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { CamelCasePlugin, Kysely, ParseJSONResultsPlugin } from "kysely";
import type { InlangDatabaseSchema } from "./schema.js";
import { createDialect, type SqliteDatabase } from "sqlite-wasm-kysely";
import { v4 } from "uuid";
import { generateBundleId } from "../bundle-id/bundle-id.js";
import { SerializeJsonPlugin } from "./serializeJsonPlugin.js";

export function initDb(args: { sqlite: SqliteDatabase }) {
initDefaultValueFunctions({ sqlite: args.sqlite });
const db = new Kysely<InlangDatabaseSchema>({
dialect: createDialect({
database: args.sqlite,
}),
plugins: [
new ParseJSONResultsPlugin(),
new CamelCasePlugin(),
new SerializeJsonPlugin(),
],
});
return db;
}

function initDefaultValueFunctions(args: { sqlite: SqliteDatabase }) {
args.sqlite.createFunction({
name: "uuid_v4",
arity: 0,
xFunc: () => v4(),
});
args.sqlite.createFunction({
name: "bundle_id",
arity: 0,
xFunc: () => generateBundleId(),
});
}
18 changes: 0 additions & 18 deletions inlang/source-code/sdk2/src/database/initKysely.ts

This file was deleted.

15 changes: 15 additions & 0 deletions inlang/source-code/sdk2/src/database/schema.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import type { Selectable } from "kysely";
import type { Bundle, Message, Variant } from "../schema/schemaV2.js";
import type { InlangDatabaseSchema } from "./schema.js";

let _;

// expect a bundle to equal the type that the database returns
_ = {} as Bundle satisfies Selectable<InlangDatabaseSchema["bundle"]>;

// expect a message to equal the type that the database returns
_ = {} as Message satisfies Selectable<InlangDatabaseSchema["message"]>;

// expect a variant to equal the type that the database returns
_ = {} as Variant satisfies Selectable<InlangDatabaseSchema["variant"]>;
48 changes: 45 additions & 3 deletions inlang/source-code/sdk2/src/database/schema.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,49 @@
import type { Generated, Insertable, Selectable, Updateable } from "kysely";
import type { Bundle, Message, Variant } from "../schema/schemaV2.js";

export type InlangDatabaseSchema = {
bundle: Bundle;
message: Message;
variant: Variant;
bundle: BundleTable;
message: MessageTable;
variant: VariantTable;
};

type BundleTable = Omit<Bundle, "id"> & {
id: Generated<string>;
};

type MessageTable = Omit<Message, "id"> & {
id: Generated<string>;
};

type VariantTable = Omit<Variant, "id"> & {
id: Generated<string>;
};

export type NewBundle = Insertable<BundleTable>;
export type BundleUpdate = Updateable<BundleTable>;

export type NewMessage = Insertable<MessageTable>;
export type MessageUpdate = Updateable<MessageTable>;

export type NewVariant = Selectable<VariantTable>;
export type VariantUpdate = Updateable<VariantTable>;

export type MessageNested = Message & {
variants: Variant[];
};
export type NewMessageNested = NewMessage & {
variants: NewVariant[];
};
export type MessageNestedUpdate = Updateable<MessageTable> & {
variants: VariantUpdate[];
};

export type BundleNested = Bundle & {
messages: MessageNested[];
};
export type NewBundleNested = NewBundle & {
messages: NewMessageNested[];
};
export type BundleNestedUpdate = BundleUpdate & {
messages: MessageNestedUpdate[];
};
90 changes: 90 additions & 0 deletions inlang/source-code/sdk2/src/database/serializeJsonPlugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {
OperationNodeTransformer,
sql,
ValueListNode,
ValueNode,
ValuesNode,
type KyselyPlugin,
type PluginTransformQueryArgs,
type PluginTransformResultArgs,
type QueryResult,
type RootOperationNode,
type UnknownRow,
} from "kysely";

export class SerializeJsonPlugin implements KyselyPlugin {
#parseJsonTransformer = new ParseJsonTransformer();

transformQuery(args: PluginTransformQueryArgs): RootOperationNode {
if (
args.node.kind === "InsertQueryNode" ||
args.node.kind === "UpdateQueryNode"
) {
const result = this.#parseJsonTransformer.transformNode(args.node);

return result;
}
return args.node;
}

async transformResult(
args: PluginTransformResultArgs
): Promise<QueryResult<UnknownRow>> {
return args.result;
}
}

class ParseJsonTransformer extends OperationNodeTransformer {
protected override transformValueList(node: ValueListNode): ValueListNode {
return super.transformValueList({
...node,
values: node.values.map((listNodeItem) => {
if (listNodeItem.kind !== "ValueNode") {
return listNodeItem;
}

// @ts-ignore
const { value } = listNodeItem;

const serializedValue = serializeJson(value);

if (value === serializedValue) {
return listNodeItem;
}

// TODO use jsonb. depends on parsing again
// https://github.com/opral/inlang-sdk/issues/132
return sql`json(${serializedValue})`.toOperationNode();
}),
});
}

override transformValues(node: ValuesNode): ValuesNode {
return super.transformValues({
...node,
values: node.values.map((valueItemNode) => {
if (valueItemNode.kind !== "PrimitiveValueListNode") {
return valueItemNode;
}

return {
kind: "ValueListNode",
values: valueItemNode.values.map(
(value) =>
({
kind: "ValueNode",
value,
} as ValueNode)
),
} as ValueListNode;
}),
});
}
}

function serializeJson(value: any): string {
if (typeof value === "object" || Array.isArray(value)) {
return JSON.stringify(value);
}
return value;
}
10 changes: 2 additions & 8 deletions inlang/source-code/sdk2/src/helper.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
// @ts-ignore
import { v4 as uuid } from "uuid";
import type {
Bundle,
BundleNested,
Expression,
MessageNested,
Text,
Variant,
} from "./schema/schemaV2.js";
import type { Bundle, Expression, Text, Variant } from "./schema/schemaV2.js";
import type { ProjectSettings } from "./schema/settings.js";
import { generateBundleId } from "./bundle-id/bundle-id.js";
import type { BundleNested, MessageNested } from "./database/schema.js";

/**
* create v2 Bundle with a random human ID
Expand Down
1 change: 1 addition & 0 deletions inlang/source-code/sdk2/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ export {
export type { InlangDatabaseSchema } from "./database/schema.js";
export type { ResourceFile } from "./project/api.js";
export type { InlangPlugin } from "./plugin/schema.js";
export * from "./database/schema.js";
Loading

0 comments on commit 0a859f0

Please sign in to comment.