Skip to content

Commit

Permalink
Enforce stricter types & type tests (#58)
Browse files Browse the repository at this point in the history
* Minor helper type refactoring

* Added `expect-type` dev dependency

* Upgraded `@ronin/compiler` to version `0.17.4`

* Added failover for `Fields` type argument in `model` function

* Added type guard tests to model tests

* Added strict type checks to all possible tests

* Fixed `sql` explicit types
  • Loading branch information
NuroDev authored Feb 19, 2025
1 parent 45124db commit a8d92e5
Show file tree
Hide file tree
Showing 9 changed files with 321 additions and 44 deletions.
Binary file modified bun.lockb
Binary file not shown.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@
"license": "Apache-2.0",
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@ronin/compiler": "0.17.2",
"@ronin/compiler": "0.17.5",
"@types/bun": "1.2.1",
"expect-type": "1.1.0",
"tsup": "8.3.6",
"typescript": "5.7.3"
}
Expand Down
12 changes: 7 additions & 5 deletions src/helpers/expressions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ import { QUERY_SYMBOLS } from '@ronin/compiler';
*
* @returns An object containing the expression wrapped in a query symbol.
*/
export const expression = (
export const expression = <T = Record<typeof QUERY_SYMBOLS.EXPRESSION, string>>(
expression: string,
): Record<typeof QUERY_SYMBOLS.EXPRESSION, string> => {
return { [QUERY_SYMBOLS.EXPRESSION]: expression };
};
): T => ({ [QUERY_SYMBOLS.EXPRESSION]: expression }) as T;

/** Valid operators for string concatenation */
type StringOperator = '||';
Expand All @@ -38,16 +36,19 @@ export const op = <
operator: NumberOperator | ComparisonOperator | StringOperator,
right: T,
): T => {
// Unwrap the left and right operands if they are expression objects
let leftValue = left;
if (typeof left === 'object' && QUERY_SYMBOLS.EXPRESSION in left) {
leftValue = left[QUERY_SYMBOLS.EXPRESSION] as T;
}

// Unwrap the right operand if it is an expression object
let rightValue = right;
if (typeof right === 'object' && QUERY_SYMBOLS.EXPRESSION in right) {
rightValue = right[QUERY_SYMBOLS.EXPRESSION] as T;
}

// Wrap the left and right operands in single quotes if they are strings
let wrappedLeft = leftValue;
if (
typeof leftValue === 'string' &&
Expand All @@ -59,6 +60,7 @@ export const op = <
wrappedLeft = `'${leftValue}'` as T;
}

// Wrap the right operand in single quotes if it is a string
let wrappedRight = rightValue;
if (
typeof rightValue === 'string' &&
Expand All @@ -70,5 +72,5 @@ export const op = <
wrappedRight = `'${rightValue}'` as T;
}

return expression(`(${wrappedLeft} ${operator} ${wrappedRight})`) as unknown as T;
return expression<T>(`(${wrappedLeft} ${operator} ${wrappedRight})`);
};
47 changes: 17 additions & 30 deletions src/helpers/functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,14 @@ import { QUERY_SYMBOLS, getQuerySymbol } from '@ronin/compiler';
*
* @returns The wrapped SQL expression
*/
export const sql = (expressions: string): any => {
return expression(expressions);
};
export const sql = (expressions: string): any => expression<any>(expressions);

/**
* Generates a pseudo-random integer between -9223372036854775808 and +9223372036854775807.
*
* @returns SQL expression that evaluates to a random number.
*/
export const random = (): number => {
return expression('random()') as unknown as number;
};
export const random = (): number => expression<number>('random()');

/**
* Calculates the absolute value of a number.
Expand All @@ -34,7 +30,8 @@ export const abs = (value: number | Record<string, string | number>): number =>
typeof value === 'object' && QUERY_SYMBOLS.EXPRESSION in value
? value[QUERY_SYMBOLS.EXPRESSION]
: value;
return expression(`abs(${valueExpression})`) as unknown as number;

return expression<number>(`abs(${valueExpression})`);
};

/**
Expand All @@ -45,9 +42,8 @@ export const abs = (value: number | Record<string, string | number>): number =>
*
* @returns SQL expression that evaluates to the formatted timestamp.
*/
export const strftime = (format: string, timestamp: string | 'now'): Date => {
return expression(`strftime('${format}', '${timestamp}')`) as unknown as Date;
};
export const strftime = (format: string, timestamp: string | 'now'): Date =>
expression<Date>(`strftime('${format}', '${timestamp}')`);

/**
* Applies a JSON patch operation to a JSON document.
Expand All @@ -57,9 +53,8 @@ export const strftime = (format: string, timestamp: string | 'now'): Date => {
*
* @returns SQL expression that evaluates to the patched JSON document.
*/
export const json_patch = (patch: string, input: string): string => {
return expression(`json_patch('${patch}', '${input}')`) as unknown as string;
};
export const json_patch = (patch: string, input: string): string =>
expression<string>(`json_patch('${patch}', '${input}')`);

/**
* Sets a value in a JSON document at the specified path.
Expand All @@ -71,9 +66,8 @@ export const json_patch = (patch: string, input: string): string => {
*
* @returns SQL expression that evaluates to the modified JSON document.
*/
export const json_set = (json: string, path: string, value: string): string => {
return expression(`json_set('${json}', '${path}', '${value}')`) as unknown as string;
};
export const json_set = (json: string, path: string, value: string): string =>
expression<string>(`json_set('${json}', '${path}', '${value}')`);

/**
* Replaces a value in a JSON document at the specified path.
Expand All @@ -85,11 +79,8 @@ export const json_set = (json: string, path: string, value: string): string => {
*
* @returns SQL expression that evaluates to the modified JSON document.
*/
export const json_replace = (json: string, path: string, value: string): string => {
return expression(
`json_replace('${json}', '${path}', '${value}')`,
) as unknown as string;
};
export const json_replace = (json: string, path: string, value: string): string =>
expression<string>(`json_replace('${json}', '${path}', '${value}')`);

/**
* Inserts a value into a JSON document at the specified path.
Expand All @@ -101,9 +92,8 @@ export const json_replace = (json: string, path: string, value: string): string
*
* @returns SQL expression that evaluates to the modified JSON document.
*/
export const json_insert = (json: string, path: string, value: string): string => {
return expression(`json_insert('${json}', '${path}', '${value}')`) as unknown as string;
};
export const json_insert = (json: string, path: string, value: string): string =>
expression<string>(`json_insert('${json}', '${path}', '${value}')`);

/**
* Concatenates a list of strings together.
Expand All @@ -120,7 +110,7 @@ export const concat = (
return symbol?.type === 'expression' ? symbol.value : `'${value}'`;
});

return expression(`concat(${formattedValues.join(', ')})`) as unknown as string;
return expression<string>(`concat(${formattedValues.join(', ')})`);
};

/**
Expand All @@ -132,8 +122,5 @@ export const concat = (
*
* @returns SQL expression that evaluates to the modified string.
*/
export const replace = (input: string, search: string, replacement: string): string => {
return expression(
`replace('${input}', '${search}', '${replacement}')`,
) as unknown as string;
};
export const replace = (input: string, search: string, replacement: string): string =>
expression<string>(`replace('${input}', '${search}', '${replacement}')`);
5 changes: 4 additions & 1 deletion src/schema/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,10 @@ type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
*
* @returns The generated model definition.
*/
export const model = <Fields extends RecordWithoutForbiddenKeys<Primitives>>(
export const model = <
// biome-ignore lint/complexity/noBannedTypes: `Fields` requires an empty object as a fallback.
Fields extends RecordWithoutForbiddenKeys<Primitives> = {},
>(
model: Model<Fields> | (() => Model<Fields>),
): Expand<RoninFields & FieldsToTypes<Fields>> => {
return getSyntaxProxy({ modelType: true, chaining: false })(model) as unknown as Expand<
Expand Down
Loading

0 comments on commit a8d92e5

Please sign in to comment.