@witchcraft/expressit 0.2.1 → 0.3.0

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 (119) hide show
  1. package/README.md +6 -4
  2. package/dist/Lexer.d.ts +102 -99
  3. package/dist/Lexer.d.ts.map +1 -1
  4. package/dist/Lexer.js +211 -557
  5. package/dist/Parser.d.ts +27 -27
  6. package/dist/Parser.d.ts.map +1 -1
  7. package/dist/Parser.js +5 -2
  8. package/dist/ast/builders/condition.d.ts +1 -1
  9. package/dist/ast/builders/condition.d.ts.map +1 -1
  10. package/dist/ast/builders/condition.js +1 -0
  11. package/dist/ast/builders/delim.d.ts +3 -3
  12. package/dist/ast/builders/delim.d.ts.map +1 -1
  13. package/dist/ast/builders/error.d.ts +2 -2
  14. package/dist/ast/builders/error.d.ts.map +1 -1
  15. package/dist/ast/builders/expression.d.ts +2 -2
  16. package/dist/ast/builders/expression.d.ts.map +1 -1
  17. package/dist/ast/builders/expression.js +2 -6
  18. package/dist/ast/builders/group.d.ts +1 -1
  19. package/dist/ast/builders/group.d.ts.map +1 -1
  20. package/dist/ast/builders/group.js +1 -3
  21. package/dist/ast/builders/pos.d.ts +2 -2
  22. package/dist/ast/builders/pos.d.ts.map +1 -1
  23. package/dist/ast/builders/token.d.ts +2 -2
  24. package/dist/ast/builders/token.d.ts.map +1 -1
  25. package/dist/ast/builders/type.d.ts +2 -2
  26. package/dist/ast/builders/type.d.ts.map +1 -1
  27. package/dist/ast/builders/variable.d.ts +3 -3
  28. package/dist/ast/builders/variable.d.ts.map +1 -1
  29. package/dist/ast/createConditionNode.d.ts +1 -1
  30. package/dist/ast/createConditionNode.d.ts.map +1 -1
  31. package/dist/ast/createGroupNode.d.ts +1 -1
  32. package/dist/ast/createGroupNode.d.ts.map +1 -1
  33. package/dist/ast/createToken.d.ts +2 -2
  34. package/dist/ast/createToken.d.ts.map +1 -1
  35. package/dist/ast/createToken.js +2 -2
  36. package/dist/ast/error.d.ts +2 -2
  37. package/dist/ast/error.d.ts.map +1 -1
  38. package/dist/ast/error.js +1 -0
  39. package/dist/ast/handlers.d.ts +23 -23
  40. package/dist/ast/handlers.d.ts.map +1 -1
  41. package/dist/ast/handlers.js +4 -4
  42. package/dist/examples/ParserWithSqlSupport.d.ts +62 -0
  43. package/dist/examples/ParserWithSqlSupport.d.ts.map +1 -0
  44. package/dist/examples/ParserWithSqlSupport.js +271 -0
  45. package/dist/examples/{shortcutContextParser.d.ts → ShortcutContextParser.d.ts} +5 -5
  46. package/dist/examples/ShortcutContextParser.d.ts.map +1 -0
  47. package/dist/examples/{shortcutContextParser.js → ShortcutContextParser.js} +2 -2
  48. package/dist/examples/index.d.ts +2 -1
  49. package/dist/examples/index.d.ts.map +1 -1
  50. package/dist/examples/index.js +3 -1
  51. package/dist/index.js +2 -2
  52. package/dist/internal/ExpressitError.d.ts +2 -2
  53. package/dist/internal/ExpressitError.d.ts.map +1 -1
  54. package/dist/internal/ExpressitError.js +2 -1
  55. package/dist/internal/checkParserOpts.d.ts +1 -1
  56. package/dist/internal/checkParserOpts.d.ts.map +1 -1
  57. package/dist/internal/checkParserOpts.js +11 -11
  58. package/dist/internal/parseParserOptions.d.ts +1 -1
  59. package/dist/internal/parseParserOptions.d.ts.map +1 -1
  60. package/dist/package.json.js +4 -195
  61. package/dist/types/ast.d.ts +60 -58
  62. package/dist/types/ast.d.ts.map +1 -1
  63. package/dist/types/ast.js +26 -27
  64. package/dist/types/autocomplete.d.ts +23 -21
  65. package/dist/types/autocomplete.d.ts.map +1 -1
  66. package/dist/types/autocomplete.js +24 -21
  67. package/dist/types/errors.d.ts +12 -10
  68. package/dist/types/errors.d.ts.map +1 -1
  69. package/dist/types/errors.js +8 -7
  70. package/dist/types/index.js +2 -2
  71. package/dist/types/parser.d.ts +9 -9
  72. package/dist/types/parser.d.ts.map +1 -1
  73. package/dist/utils/getCursorInfo.js +3 -1
  74. package/dist/utils/getOppositeDelimiter.d.ts +2 -2
  75. package/dist/utils/getOppositeDelimiter.d.ts.map +1 -1
  76. package/dist/utils/isDelimiter.d.ts +2 -2
  77. package/dist/utils/isDelimiter.d.ts.map +1 -1
  78. package/dist/utils/isParen.d.ts +2 -2
  79. package/dist/utils/isParen.d.ts.map +1 -1
  80. package/dist/utils/isQuote.d.ts +2 -2
  81. package/dist/utils/isQuote.d.ts.map +1 -1
  82. package/package.json +29 -27
  83. package/src/Lexer.ts +103 -92
  84. package/src/Parser.ts +70 -64
  85. package/src/ast/builders/condition.ts +3 -3
  86. package/src/ast/builders/delim.ts +5 -5
  87. package/src/ast/builders/error.ts +3 -3
  88. package/src/ast/builders/expression.ts +4 -8
  89. package/src/ast/builders/group.ts +2 -4
  90. package/src/ast/builders/pos.ts +3 -3
  91. package/src/ast/builders/token.ts +2 -2
  92. package/src/ast/builders/type.ts +2 -2
  93. package/src/ast/builders/variable.ts +5 -5
  94. package/src/ast/createConditionNode.ts +2 -2
  95. package/src/ast/createGroupNode.ts +4 -4
  96. package/src/ast/createToken.ts +6 -6
  97. package/src/ast/error.ts +2 -2
  98. package/src/ast/handlers.ts +23 -23
  99. package/src/examples/ParserWithSqlSupport.ts +371 -0
  100. package/src/examples/{shortcutContextParser.ts → ShortcutContextParser.ts} +14 -14
  101. package/src/examples/index.ts +2 -1
  102. package/src/internal/ExpressitError.ts +4 -4
  103. package/src/internal/checkParserOpts.ts +14 -14
  104. package/src/internal/parseParserOptions.ts +2 -2
  105. package/src/types/ast.ts +101 -96
  106. package/src/types/autocomplete.ts +26 -22
  107. package/src/types/errors.ts +18 -13
  108. package/src/types/parser.ts +9 -9
  109. package/src/utils/getCursorInfo.ts +1 -1
  110. package/src/utils/getOppositeDelimiter.ts +2 -2
  111. package/src/utils/getSurroundingErrors.ts +4 -4
  112. package/src/utils/isDelimiter.ts +3 -3
  113. package/src/utils/isParen.ts +2 -2
  114. package/src/utils/isQuote.ts +2 -2
  115. package/dist/examples/shortcutContextParser.d.ts.map +0 -1
  116. package/dist/global.d.js +0 -1
  117. package/dist/package.js +0 -7
  118. package/src/global.d.ts +0 -4
  119. package/src/package.js +0 -11
@@ -9,15 +9,15 @@ export function createGroupNode<
9
9
  TPrefix extends
10
10
  TPrefixable extends true
11
11
  ? ConditionNode<TValid> |
12
- ValidToken<TOKEN_TYPE.NOT> |
12
+ ValidToken<typeof TOKEN_TYPE.NOT> |
13
13
  undefined
14
- : ValidToken<TOKEN_TYPE.NOT> |
14
+ : ValidToken<typeof TOKEN_TYPE.NOT> |
15
15
  undefined =
16
16
  TPrefixable extends true
17
17
  ? ConditionNode<TValid> |
18
- ValidToken<TOKEN_TYPE.NOT> |
18
+ ValidToken<typeof TOKEN_TYPE.NOT> |
19
19
  undefined
20
- : ValidToken<TOKEN_TYPE.NOT>,
20
+ : ValidToken<typeof TOKEN_TYPE.NOT>,
21
21
  >(raw: {
22
22
  prefix: TPrefix
23
23
  } & RawNode<GroupNode<TValid>>): GroupNode<TValid, TPrefixable, TPrefix> {
@@ -1,21 +1,21 @@
1
1
  import { ExpressitError } from "../internal/ExpressitError.js"
2
- import type { ErrorToken, RawToken, TOKEN_TYPE, ValidToken } from "../types/ast.js"
3
- import { ERROR_CODES } from "../types/errors.js"
2
+ import type { ErrorToken, RawToken, TokenType, ValidToken } from "../types/ast.js"
3
+ import { PARSER_ERROR } from "../types/errors.js"
4
4
 
5
5
  export function createToken<
6
- TType extends TOKEN_TYPE,
6
+ TType extends TokenType,
7
7
  >(raw: { type: TType } & RawToken<ValidToken>): ValidToken<TType>
8
8
  export function createToken(raw: RawToken<ErrorToken>): ErrorToken
9
9
  export function createToken<
10
10
  TValid extends boolean = boolean,
11
11
  TType extends
12
- TValid extends true ? TOKEN_TYPE : never =
13
- TValid extends true ? TOKEN_TYPE : never,
12
+ TValid extends true ? TokenType : never =
13
+ TValid extends true ? TokenType : never
14
14
  >(raw: RawToken<ValidToken> | RawToken<ErrorToken>): TValid extends true
15
15
  ? ValidToken<TType>
16
16
  : ErrorToken {
17
17
  if (raw.start === undefined || raw.end === undefined) {
18
- throw new ExpressitError(ERROR_CODES.PARSER_POSITION_ERROR, raw)
18
+ throw new ExpressitError(PARSER_ERROR.POSITION_ERROR, raw)
19
19
  }
20
20
  return {
21
21
  ...raw,
package/src/ast/error.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import { createToken } from "./createToken.js"
2
2
 
3
- import { type ErrorToken,type TOKEN_TYPE } from "../types/ast.js"
3
+ import { type ErrorToken,type TokenType } from "../types/ast.js"
4
4
 
5
- export function error<T extends TOKEN_TYPE>(pos: number, expected: T[]): ErrorToken {
5
+ export function error<T extends TokenType>(pos: number, expected: T[]): ErrorToken {
6
6
  if (pos === undefined) throw new Error("should never happen, passed undefined position for error token")
7
7
  return createToken({ expected, start: pos, end: pos })
8
8
  }
@@ -7,28 +7,28 @@ import { createGroupNode } from "./createGroupNode.js"
7
7
  import { createToken } from "./createToken.js"
8
8
  import { createVariableNode } from "./createVariableNode.js"
9
9
 
10
- import { type AnyToken, type ArrayNode,AST_TYPE,type ConditionNode, type ErrorToken, type ExpressionNode,type FirstParam,type GroupNode, type Position, TOKEN_TYPE, type TokenBooleanTypes, type TokenDelimiterTypes, type TokenOperatorTypes, type TokenQuoteTypes, type ValidToken, type VariableNode } from "../types/ast.js"
10
+ import { type AnyToken, type ArrayNode,AST_TYPE,type ConditionNode, type ErrorToken, type ExpressionNode,type FirstParam,type GroupNode, type Position, TOKEN_TYPE, type TokenBoolean, type TokenDelimiter, type TokenOperator, type TokenQuote, type TokenType, type ValidToken, type VariableNode } from "../types/ast.js"
11
11
 
12
12
 
13
13
  /* #region HELPERS */
14
- function error<T extends TOKEN_TYPE>(pos: number, expected: T[]): ErrorToken {
14
+ function error<T extends TokenType>(pos: number, expected: T[]): ErrorToken {
15
15
  if (pos === undefined) throw new Error("should never happen, passed undefined position for error token")
16
16
  return createToken({ expected, start: pos, end: pos })
17
17
  }
18
18
  /* #regionend */
19
19
 
20
20
  /* #region TOKENS */
21
- const operators = <T extends TokenOperatorTypes>
21
+ const operators = <T extends TokenOperator>
22
22
  (type: T) =>
23
23
  (value: string, pos: Position): ValidToken<T> => createToken({ value, type, ...pos })
24
24
 
25
- const delimiters = <T extends TokenDelimiterTypes>
25
+ const delimiters = <T extends TokenDelimiter>
26
26
  (type: T) =>
27
27
  (value: string | undefined, pos: Position): ValidToken<T> | undefined =>
28
28
  // check must be falsy, we want to return undefined when given ""
29
29
  value ? createToken({ value, type, ...pos }) : undefined
30
30
 
31
- const maybeToken = <T extends TOKEN_TYPE> (type: T) => <TVal extends string | undefined> (value: TVal, pos: Position): TVal extends string ? ValidToken<T> : ErrorToken => {
31
+ const maybeToken = <T extends TokenType> (type: T) => <TVal extends string | undefined> (value: TVal, pos: Position): TVal extends string ? ValidToken<T> : ErrorToken => {
32
32
  if (value === undefined) {
33
33
  return error(pos.end, [type]) as any
34
34
  } else {
@@ -63,11 +63,11 @@ export const operator = {
63
63
 
64
64
  /* #region AST NODES */
65
65
  export function variable(
66
- prefix: ValidToken<TOKEN_TYPE.VALUE> | null | undefined,
67
- quoteL: AnyToken<TokenQuoteTypes> | null | undefined,
68
- value: AnyToken<TOKEN_TYPE.VALUE> | null | undefined,
69
- quoteR: AnyToken<TokenQuoteTypes> | null | undefined,
70
- flags?: ValidToken<TOKEN_TYPE.VALUE>,
66
+ prefix: ValidToken<typeof TOKEN_TYPE.VALUE> | null | undefined,
67
+ quoteL: AnyToken<TokenQuote> | null | undefined,
68
+ value: AnyToken<typeof TOKEN_TYPE.VALUE> | null | undefined,
69
+ quoteR: AnyToken<TokenQuote> | null | undefined,
70
+ flags?: ValidToken<typeof TOKEN_TYPE.VALUE>,
71
71
  ): VariableNode {
72
72
  const node: FirstParam<typeof createVariableNode> = {
73
73
  prefix: prefix ?? undefined,
@@ -92,19 +92,19 @@ export function variable(
92
92
 
93
93
 
94
94
  export function condition(
95
- not: ValidToken<TOKEN_TYPE.NOT> | null | undefined,
95
+ not: ValidToken<typeof TOKEN_TYPE.NOT> | null | undefined,
96
96
  property: VariableNode | null | undefined,
97
97
  { propertyOperator, sepL, sepR }: {
98
98
  propertyOperator?: ConditionNode["propertyOperator"] | null
99
- sepL?: ValidToken<TOKEN_TYPE.OP_EXPANDED_SEP> | null
100
- sepR?: ValidToken<TOKEN_TYPE.OP_EXPANDED_SEP> | null
99
+ sepL?: ValidToken<typeof TOKEN_TYPE.OP_EXPANDED_SEP> | null
100
+ sepR?: ValidToken<typeof TOKEN_TYPE.OP_EXPANDED_SEP> | null
101
101
  } = {},
102
102
  value?: VariableNode | GroupNode | ArrayNode | null,
103
103
  ): ConditionNode {
104
104
  const start = (not?.start ?? property?.start ?? sepL?.start ?? propertyOperator?.start ?? sepR?.start ?? value?.start)!
105
105
  const end = (value?.end ?? sepR?.end ?? propertyOperator?.end ?? sepL?.end ?? property?.end ?? not?.end)!
106
106
  const node: FirstParam<typeof createConditionNode> = {
107
- value: value ? value : error(end, [TOKEN_TYPE.VALUE]),
107
+ value: value ?? error(end, [TOKEN_TYPE.VALUE]),
108
108
  start,
109
109
  end,
110
110
  }
@@ -115,22 +115,22 @@ export function condition(
115
115
  node.sep = {}
116
116
  if (sepL) {
117
117
  node.sep.left = sepL
118
- node.property ||= error(sepL.start, [TOKEN_TYPE.VALUE])
119
- node.propertyOperator ||= error(sepL?.end ?? sepR?.start, [TOKEN_TYPE.VALUE])
118
+ node.property ??= error(sepL.start, [TOKEN_TYPE.VALUE])
119
+ node.propertyOperator ??= error(sepL?.end ?? sepR?.start, [TOKEN_TYPE.VALUE])
120
120
  }
121
121
  if (sepR) node.sep.right = sepR
122
122
  else if (!node.value || node.value.type === AST_TYPE.VARIABLE) {
123
123
  node.sep.right = error(node.value?.start ?? end, [TOKEN_TYPE.OP_EXPANDED_SEP])
124
124
  }
125
125
  } else if (propertyOperator) {
126
- node.property ||= error(propertyOperator.start, [TOKEN_TYPE.VALUE])
126
+ node.property ??= error(propertyOperator.start, [TOKEN_TYPE.VALUE])
127
127
  }
128
128
  return createConditionNode(node as ConditionNode)
129
129
  }
130
130
 
131
131
  export function expression(
132
132
  left: ConditionNode | GroupNode | null | undefined,
133
- operator: ValidToken<TokenBooleanTypes> | null | undefined,
133
+ operator: ValidToken<TokenBoolean> | null | undefined,
134
134
  right: ConditionNode | GroupNode | null | undefined,
135
135
  ): ExpressionNode {
136
136
  return createExpressionNode({
@@ -143,11 +143,11 @@ export function expression(
143
143
  }
144
144
 
145
145
  export function group(
146
- operator: ValidToken<TOKEN_TYPE.NOT> | null | undefined,
146
+ operator: ValidToken<typeof TOKEN_TYPE.NOT> | null | undefined,
147
147
  prefix: ConditionNode | null | undefined,
148
- parenL: ValidToken<TOKEN_TYPE.PARENL> | null | undefined,
148
+ parenL: ValidToken<typeof TOKEN_TYPE.PARENL> | null | undefined,
149
149
  condition: GroupNode["expression"],
150
- parenR: ValidToken<TOKEN_TYPE.PARENR> | null | undefined,
150
+ parenR: ValidToken<typeof TOKEN_TYPE.PARENR> | null | undefined,
151
151
  ): GroupNode {
152
152
  return createGroupNode({
153
153
  prefix: prefix ?? operator ?? undefined,
@@ -162,9 +162,9 @@ export function group(
162
162
  }
163
163
 
164
164
  export function array(
165
- bracketL: ValidToken<TOKEN_TYPE.BRACKETL>,
165
+ bracketL: ValidToken<typeof TOKEN_TYPE.BRACKETL>,
166
166
  values: VariableNode[],
167
- bracketR: ValidToken<TOKEN_TYPE.BRACKETR> | null | undefined,
167
+ bracketR: ValidToken<typeof TOKEN_TYPE.BRACKETR> | null | undefined,
168
168
  ): ArrayNode {
169
169
  return createArrayNode({
170
170
  values,
@@ -0,0 +1,371 @@
1
+ import { unreachable } from "@alanscodelog/utils/unreachable.js"
2
+
3
+ import { Parser } from "../Parser.js"
4
+ import { AST_TYPE, type NormalizedCondition, type NormalizedExpression, type Position,TOKEN_TYPE } from "../types/ast.js"
5
+
6
+ export interface BasePropertyDefinition {
7
+ name: string
8
+ /** Supported types are: string, boolean, int, float, date. */
9
+ type: string
10
+ /** Return how to access the column in the WHERE condition of the SQL query, useful for json properties. It is up to you to properly quote the value if needed. */
11
+ transformToColumn?: (key: string, name: string) => string
12
+ /**
13
+ * A function that can be used to transform the value before it is inserted into the SQL query. Useful for types like arrays. It is up to you to properly escape values if you use the second parameter which contains the unescaped value.
14
+ */
15
+ transformValue?: (value: any, unescapedValue: any) => any | any[]
16
+ isArray?: boolean
17
+ /** If undefined, it's assumed all operators are supported. This should only include the final operator (see {@link BaseOperatorDefinition.operator}), it doesn't need to include it's aliases. */
18
+ supportedOperators?: string[]
19
+ /** Further transform the value after the basic parsing has been done. Useful to, for example, parse other strings (e.g. now, today, tomorrow) as dates. */
20
+ postParse?: (value: any) => any
21
+ }
22
+ export interface BaseOperatorDefinition {
23
+ /** The final operator to use in the SQL query and while evaluating. */
24
+ operator: string
25
+ /** All aliases the user can use to specify the operator. They need not include the real final operator. */
26
+ operators: string[]
27
+ /** All negated aliases to the operator. If an operator is listed here, it will be used to "invert" a condition when normalizing. e.g. < can list >= as a negated operator. This greatly simplifies queries. */
28
+ negatedOperators?: string[]
29
+ /** How to compare the value when evualuating a condition. This is only used if using `evaluate`. */
30
+ valueComparer: (condition: any, contextValue: any) => boolean
31
+ }
32
+ export type SqlParserError =
33
+ | "invalidKey"
34
+ | "unknownProperty"
35
+ | "unknownOperator"
36
+ | "unknownOperatorForType"
37
+ | "invalidValueType"
38
+ /*
39
+ * Creates an example parser with a `toSql` method that can be used to convert an AST to a a safe SQL query.
40
+ *
41
+ * The parser assumes all column names (i.e. the propertyDefinitions) and operators (i.e. operatorDefinitions) are vetted and safe to use in the SQL queries. They are double quoted (unless transformToColumn is specificed) so you can use special characters if needed. You can use the `transformToColumn` option to specify a custom name (e.g. for json columns) that will be left as is.
42
+ *
43
+ * Values are escaped with the given `sqlEscapeValue`. If using drizzle, for simple tables and types, you can just do and it'll be safe. More complex types, such as arrays, or json properties are more comlicated and you'll probably want to specify the definition's transformValue option. See the examples tests for examples.
44
+ * ```ts
45
+ * const parser = new ParserWithSqlSupport(
46
+ * propertyDefinitions,
47
+ * {
48
+ * sqlEscapeValue: (value: string) => sql`${value}`
49
+ * }
50
+ * )
51
+ * ```
52
+ *
53
+ */
54
+ export class ParserWithSqlSupport<TErrorToken extends
55
+ Position & {
56
+ type: SqlParserError
57
+ message?: string
58
+ } =
59
+ Position & {
60
+ type: SqlParserError
61
+ message?: string
62
+ },
63
+ TPropertyDefinition extends BasePropertyDefinition = BasePropertyDefinition,
64
+ TPropertyDefinitions extends Record<string, TPropertyDefinition> = Record<string, TPropertyDefinition>,
65
+ TOperatorDefinition extends BaseOperatorDefinition = BaseOperatorDefinition,
66
+ TOperatorDefinitions extends Record<string, TOperatorDefinition> = Record<string, TOperatorDefinition>,
67
+ TSqlEscapeValue extends (value: string) => any | ReturnType<TSqlEscapeValue> = (value: string) => any,
68
+ > extends Parser<TErrorToken> {
69
+ sqlEscapeValue: TSqlEscapeValue
70
+
71
+ operatorMap: Record<string, string>
72
+
73
+ propertyDefinitions: TPropertyDefinitions
74
+
75
+ operatorDefinitions: TOperatorDefinitions
76
+
77
+ constructor(
78
+ propertyDefinitions: TPropertyDefinitions,
79
+ operatorDefinitions: TOperatorDefinitions,
80
+ { sqlEscapeValue }: { sqlEscapeValue: TSqlEscapeValue }
81
+ ) {
82
+ const operators = []
83
+ const operatorMap: Record<string, string> = {}
84
+ for (const value of Object.values(operatorDefinitions)) {
85
+ for (const operator of value.operators) {
86
+ operatorMap[operator] = value.operator
87
+ operators.push(operator)
88
+ }
89
+ if (value.negatedOperators) {
90
+ for (const operator of value.negatedOperators) {
91
+ operatorMap[operator] = value.operator
92
+ operators.push(operator)
93
+ }
94
+ }
95
+ }
96
+ super({
97
+ arrayValues: true,
98
+ regexValues: false,
99
+ keywords: {
100
+ and: [{ isSymbol: true, value: "&&" }],
101
+ or: [{ isSymbol: true, value: "||" }],
102
+ not: [{ isSymbol: true, value: "!" }],
103
+ },
104
+ customPropertyOperators: operators,
105
+ prefixableGroups: false,
106
+ valueComparer: (condition, contextValue, _context) => {
107
+ if (typeof condition.value !== typeof contextValue) {
108
+ throw new Error(`Expected type of property ${condition.property[0]} to be the same type as the context value ${contextValue} (${typeof contextValue}). If the ast has been validated this is likely because the type of the context value is incorrect.`)
109
+ }
110
+
111
+ const prop = condition.property[0]
112
+ if (!prop) unreachable("Did you validate the ast before evaluating it?")
113
+
114
+ const propDefinition = propertyDefinitions[prop]
115
+ const operatorDefinition = condition.operator && operatorDefinitions[condition.operator]
116
+ if (!operatorDefinition) unreachable("Did you validate the ast before evaluating it?")
117
+
118
+ const isSupported = !propDefinition.supportedOperators?.includes(condition.operator!)
119
+ if (!isSupported) unreachable("Did you validate the ast before evaluating it?")
120
+
121
+ const res = operatorDefinition.valueComparer(condition, contextValue)
122
+ return res
123
+ },
124
+ valueValidator: (_contextValue, query): TErrorToken[] | void => {
125
+ const prop = query.propertyKeys[0]
126
+ let tokens: TErrorToken[] = []
127
+ const propDefinition = propertyDefinitions[prop]
128
+ if (!propDefinition) {
129
+ tokens = tokens.concat(query.property.map(token => ({
130
+ start: token.start,
131
+ end: token.end,
132
+ type: "unknownProperty",
133
+ })) as TErrorToken[])
134
+ return tokens
135
+ }
136
+ const op = query.operator
137
+ const opKey = op && operatorMap[op.value]
138
+ if (!op || !opKey) {
139
+ tokens.push({
140
+ start: (op ?? query.condition)?.start,
141
+ end: (op ?? query.condition)?.end,
142
+ type: "unknownOperator",
143
+ } as TErrorToken)
144
+ } else {
145
+ if (propDefinition.supportedOperators && !propDefinition.supportedOperators?.includes(opKey)) {
146
+ tokens.push({
147
+ start: query.condition.start,
148
+ end: query.condition.end,
149
+ type: "unknownOperatorForType",
150
+ } as TErrorToken)
151
+ }
152
+ }
153
+
154
+ const val = query.value
155
+ if (Array.isArray(val)) {
156
+ for (const v of val) {
157
+ if (v.type !== "VARIABLE") unreachable()
158
+ const res = convertAndValidateValue(query.isQuoted, v.value.value, prop, propertyDefinitions, { isArray: true })
159
+ if (res instanceof Error) {
160
+ if (v.type !== "VARIABLE") unreachable()
161
+ const token = v
162
+ tokens.push({
163
+ start: token.start,
164
+ end: token.end,
165
+ type: "invalidValueType",
166
+ message: res.message,
167
+ } as TErrorToken)
168
+ }
169
+ }
170
+ if (tokens.length > 0) return tokens
171
+ return
172
+ }
173
+
174
+ if (val?.type !== "VARIABLE") unreachable()
175
+ const value = val.value.value
176
+ const res = convertAndValidateValue(query.isQuoted, value, prop, propertyDefinitions)
177
+ if (res instanceof Error) {
178
+ if (!query.value || query.value.type !== "VARIABLE") unreachable()
179
+ const token = query.value.value
180
+ tokens.push({
181
+ start: token.start,
182
+ end: token.end,
183
+ type: "invalidValueType",
184
+ message: res.message,
185
+ } as TErrorToken)
186
+ }
187
+
188
+ if (tokens.length > 0) return tokens
189
+ },
190
+ conditionNormalizer(query) {
191
+ const prop = query.property?.[0]
192
+ if (!prop) unreachable("Did you validate the ast before normalizing it?")
193
+ const propDefinition = propertyDefinitions[prop]
194
+
195
+ let finalValue
196
+ if (Array.isArray(query.value)) {
197
+ const values = []
198
+ if (query.condition.value.type !== AST_TYPE.ARRAY) unreachable()
199
+ const raw = query.condition.value.values
200
+ for (let i = 0; i < query.value.length; i += 1) {
201
+ const token = raw[i]
202
+ const val = query.value[i]
203
+ const isQuoted = !!token.quote
204
+ const res = convertAndValidateValue(isQuoted, val, prop, propertyDefinitions, { isArray: true })
205
+ if (res instanceof Error) throw res
206
+ values.push(res)
207
+ }
208
+ finalValue = values
209
+ } else {
210
+ finalValue = convertAndValidateValue(query.isQuoted, query.value, prop, propertyDefinitions)
211
+ if (propDefinition.isArray) {
212
+ finalValue = [finalValue]
213
+ }
214
+ }
215
+
216
+ let finalOperator: any = query.operator
217
+ if (finalValue instanceof Error) throw finalValue
218
+ const opKey = query.operator && operatorMap[query.operator]
219
+ if (!opKey) unreachable("Did you validate the ast before normalizing it?")
220
+
221
+ const operatorDefinition = opKey && operatorDefinitions[opKey]
222
+ if (!operatorDefinition) unreachable("Did you validate the ast before normalizing it?")
223
+
224
+
225
+ const isNegatableOperator = operatorDefinition.negatedOperators?.includes(query.operator!)
226
+
227
+ finalOperator = operatorDefinition.operator
228
+ let isNegated = query.isNegated
229
+ if (isNegatableOperator) {
230
+ isNegated = !isNegated
231
+ }
232
+
233
+ return { value: finalValue, operator: finalOperator, negate: isNegated }
234
+ },
235
+ })
236
+ this.propertyDefinitions = propertyDefinitions
237
+ this.operatorDefinitions = operatorDefinitions
238
+ this.operatorMap = operatorMap
239
+ this.sqlEscapeValue = sqlEscapeValue
240
+ }
241
+
242
+ toSql<T>(
243
+ ast: NormalizedExpression<any, any> | NormalizedCondition<any, any>,
244
+ /**
245
+ * Optionally convert the raw strings to something else. If uding drizzle, you can pass sql.raw here. So later you can just sql.join the return of the function:
246
+ *
247
+ * ```ts
248
+ * sql.join([
249
+ * sql.raw(db.select().from(someTable).toSQL().sql),
250
+ * sql.raw(`where`),
251
+ * ...parser.toSql(ast, sql.raw)
252
+ * ], sql` `)
253
+ * ```
254
+ */
255
+ wrapStrings?: (value: string) => T,
256
+ ): (ReturnType<TSqlEscapeValue> | typeof wrapStrings extends undefined ? string : T)[] {
257
+ this._checkEvaluationOptions()
258
+ const chunks = []
259
+
260
+ if (ast.type === AST_TYPE.NORMALIZED_CONDITION) {
261
+ const prop = ast.property?.[0]
262
+ const definition = this.propertyDefinitions[prop]
263
+ const value = ast.value
264
+ const col = definition.transformToColumn?.(prop, definition.name) ?? `"${prop}"`
265
+ const op = ast.operator
266
+ if (ast.negate) {
267
+ chunks.push(wrapStrings?.(`NOT(`) ?? `NOT(`)
268
+ }
269
+ chunks.push(wrapStrings?.(`${col} `) ?? `${col} `)
270
+ chunks.push(wrapStrings?.(`${op} `) ?? `${op} `)
271
+ const val = this.sqlEscapeValue(value)
272
+ if (definition.transformValue) {
273
+ const transformed = definition.transformValue(val, value)
274
+ if (Array.isArray(transformed)) {
275
+ chunks.push(...transformed)
276
+ } else {
277
+ chunks.push(transformed)
278
+ }
279
+ } else {
280
+ chunks.push(val)
281
+ }
282
+ if (ast.negate) {
283
+ chunks.push(wrapStrings?.(`)`) ?? `)`)
284
+ }
285
+ return chunks
286
+ }
287
+ if (ast.type === AST_TYPE.NORMALIZED_EXPRESSION) {
288
+ const left = this.toSql(ast.left, wrapStrings)
289
+ const right = this.toSql(ast.right, wrapStrings)
290
+ const op = ast.operator === TOKEN_TYPE.AND ? "AND" : "OR"
291
+ chunks.push(wrapStrings?.(`(`) ?? `(`)
292
+ chunks.push(...left)
293
+ chunks.push(wrapStrings?.(` ${op} `) ?? ` ${op} `)
294
+ chunks.push(...right)
295
+ chunks.push(wrapStrings?.(`)`) ?? `)`)
296
+ return chunks as any
297
+ }
298
+
299
+ return unreachable()
300
+ }
301
+ }
302
+ export function createTypeError(prop: string, type: string, isArray: boolean): Error {
303
+ if (isArray) {
304
+ return new Error(`Property ${prop} must contain items of type ${type}.`)
305
+ }
306
+ return new Error(`Property ${prop} must be of type ${type}.`)
307
+ }
308
+
309
+ export function convertAndValidateValue(
310
+ isQuoted: boolean,
311
+ value: any,
312
+ prop: string,
313
+ propertyDefinitions: Record<string, BasePropertyDefinition>,
314
+ { isArray = false }: { isArray?: boolean } = {},
315
+ ): any {
316
+ let finalValue: any = value
317
+ let isFloat = false
318
+ const propDefinition = propertyDefinitions[prop]
319
+
320
+ if (typeof value === "string" && !isQuoted) {
321
+ if (finalValue === "true") {
322
+ finalValue = true
323
+ } else if (finalValue === "false") {
324
+ finalValue = false
325
+ } else {
326
+ const asNum = parseInt(value, 10)
327
+ if (!isNaN(asNum)) {
328
+ finalValue = asNum
329
+ } else {
330
+ const asFloat = parseFloat(value)
331
+ if (!isNaN(asFloat)) {
332
+ finalValue = asFloat
333
+ isFloat = true
334
+ }
335
+ }
336
+ }
337
+ }
338
+ const type = propDefinition.type
339
+ finalValue = propDefinition.postParse?.(finalValue) ?? finalValue
340
+
341
+ switch (type) {
342
+ case "integer":
343
+ case "float": {
344
+ if (typeof finalValue !== "number" || (type === "float" && !isFloat) || (type === "integer" && isFloat)) {
345
+ return createTypeError(prop, type, isArray)
346
+ }
347
+ break
348
+ }
349
+ case "string":
350
+ case "boolean": {
351
+ if (typeof finalValue !== propDefinition.type) {
352
+ return createTypeError(prop, type, isArray)
353
+ }
354
+ break
355
+ }
356
+ case "date": {
357
+ if (finalValue instanceof Date) {
358
+ break
359
+ }
360
+ const maybeDate = new Date(finalValue)
361
+ if (isNaN(maybeDate.getTime())) {
362
+ return createTypeError(prop, "date", isArray)
363
+ } else {
364
+ finalValue = maybeDate
365
+ }
366
+ break
367
+ }
368
+ }
369
+
370
+ return finalValue
371
+ }
@@ -1,3 +1,7 @@
1
+ import { Parser } from "../Parser.js"
2
+ import type { Position } from "../types/ast.js"
3
+ import type { ValueQuery } from "../types/parser.js"
4
+
1
5
  /* TODO TOUPDATE */
2
6
  /**
3
7
  * A pre-configured parser for parsing shortcut contexts (similar to VSCode's [when clause contexts](https://code.visualstudio.com/docs/getstarted/keybindings#_when-clause-contexts)).
@@ -9,15 +13,11 @@
9
13
  * The validate function will return a list of positions with a list of errors which includes handling invalid or duplicate regex flags.
10
14
  */
11
15
 
12
- import { Parser } from "../Parser.js"
13
- import type { Position } from "../types/ast.js"
14
- import type { ValueQuery } from "../types/parser.js"
15
-
16
16
 
17
- export class ShortcutContextParser<T extends
17
+ export class ShortcutContextParser<TErrorTokens extends
18
18
  Position & { type: ("invalidKey" | "unregexableKey" | "invalidRegexFlag" | "duplicateRegexFlag") } =
19
19
  Position & { type: ("invalidKey" | "unregexableKey" | "invalidRegexFlag" | "duplicateRegexFlag") },
20
- > extends Parser<T> {
20
+ > extends Parser<TErrorTokens> {
21
21
  validKeys: string[] = []
22
22
 
23
23
  regexablekeys: string[] = []
@@ -49,21 +49,21 @@ export class ShortcutContextParser<T extends
49
49
  }
50
50
  return contextValue === condition.value
51
51
  },
52
- valueValidator: (_contextValue, query): T[] | void => {
53
- let tokens: T[] = []
52
+ valueValidator: (_contextValue, query): TErrorTokens[] | void => {
53
+ let tokens: TErrorTokens[] = []
54
54
  if (!this.validKeys.includes(query.propertyName!)) {
55
55
  tokens = tokens.concat(query.property.map(token => ({
56
56
  start: token.start,
57
57
  end: token.end,
58
58
  type: "invalidKey",
59
- })) as T[])
59
+ })) as TErrorTokens[])
60
60
  }
61
61
  if (query.isRegex && !this.regexablekeys.includes(query.propertyName!)) {
62
62
  tokens = tokens.concat(query.property.map(token => ({
63
63
  start: token.start,
64
64
  end: token.end,
65
65
  type: "unregexableKey",
66
- })) as T[])
66
+ })) as TErrorTokens[])
67
67
  }
68
68
  if (query.regexFlags) {
69
69
  const chars = query.regexFlags.value.split("")
@@ -75,14 +75,14 @@ export class ShortcutContextParser<T extends
75
75
  start: start + i,
76
76
  end: start + i + 1,
77
77
  type: "duplicateRegexFlag",
78
- } as T)
78
+ } as TErrorTokens)
79
79
  }
80
80
  if (!validRegexFlags.includes(char)) {
81
81
  tokens.push({
82
82
  start: start + i,
83
83
  end: start + i + 1,
84
84
  type: "invalidRegexFlag",
85
- } as T)
85
+ } as TErrorTokens)
86
86
  }
87
87
  }
88
88
  }
@@ -93,7 +93,7 @@ export class ShortcutContextParser<T extends
93
93
  let finalOperator: any = operator
94
94
  // another way to allow special unquoted value types is something like this:
95
95
  if (typeof value === "string" && !isQuoted) {
96
- const asNum = parseInt(value, 2)
96
+ const asNum = parseInt(value, 10)
97
97
  if (!isNaN(asNum)) finalValue = asNum
98
98
  if (["true", "false"].includes(value)) {
99
99
  finalValue = value === "true"
@@ -132,7 +132,7 @@ export class ShortcutContextParser<T extends
132
132
  for (const key of Object.keys(context)) {
133
133
  if (typeof context[key] === "boolean") {
134
134
  this.validKeys.push(prev ? `${prev}.${key}` : key)
135
- if (context[key] === true) {
135
+ if (context[key]) {
136
136
  this.regexablekeys.push(prev ? `${prev}.${key}` : key)
137
137
  }
138
138
  } else {
@@ -1,3 +1,4 @@
1
1
  /* Autogenerated Index */
2
2
 
3
- export { ShortcutContextParser } from "./shortcutContextParser.js"
3
+ export { ShortcutContextParser } from "./ShortcutContextParser.js"
4
+ export { ParserWithSqlSupport } from "./ParserWithSqlSupport.js"
@@ -3,12 +3,12 @@ import { indent } from "@alanscodelog/utils/indent.js"
3
3
  import { pretty } from "@alanscodelog/utils/pretty.js"
4
4
  import type { Keys } from "@alanscodelog/utils/types"
5
5
 
6
- // @ts-expect-error todo
7
- import { repository, version } from "../package.js"
8
- import type { ERROR_CODES, ErrorInfo } from "../types/errors.js"
6
+ import packageJson from "../../package.json" with { type: "json" }
7
+ const { version, repository } = packageJson
8
+ import type { ErrorInfo, ParserError } from "../types/errors.js"
9
9
 
10
10
  /** @internal */
11
- export class ExpressitError<T extends ERROR_CODES> extends Error {
11
+ export class ExpressitError<T extends ParserError> extends Error {
12
12
  version: string = version
13
13
 
14
14
  repo: string = repository