@gblikas/querykit 0.0.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 (118) hide show
  1. package/.cursor/BUGBOT.md +21 -0
  2. package/.cursor/rules/01-project-structure.mdc +77 -0
  3. package/.cursor/rules/02-typescript-standards.mdc +105 -0
  4. package/.cursor/rules/03-testing-standards.mdc +78 -0
  5. package/.cursor/rules/04-query-language.mdc +79 -0
  6. package/.cursor/rules/05-solid-principles.mdc +118 -0
  7. package/.cursor/rules/liqe-readme-docs.mdc +438 -0
  8. package/.devcontainer/devcontainer.json +25 -0
  9. package/.eslintignore +1 -0
  10. package/.eslintrc.js +39 -0
  11. package/.github/dependabot.yml +12 -0
  12. package/.github/workflows/ci.yml +114 -0
  13. package/.github/workflows/publish.yml +61 -0
  14. package/.husky/pre-commit +30 -0
  15. package/.prettierrc +10 -0
  16. package/CONTRIBUTING.md +187 -0
  17. package/LICENSE +674 -0
  18. package/README.md +237 -0
  19. package/dist/adapters/drizzle/index.d.ts +122 -0
  20. package/dist/adapters/drizzle/index.js +166 -0
  21. package/dist/adapters/index.d.ts +7 -0
  22. package/dist/adapters/index.js +25 -0
  23. package/dist/adapters/types.d.ts +60 -0
  24. package/dist/adapters/types.js +8 -0
  25. package/dist/index.d.ts +75 -0
  26. package/dist/index.js +118 -0
  27. package/dist/parser/index.d.ts +2 -0
  28. package/dist/parser/index.js +18 -0
  29. package/dist/parser/parser.d.ts +51 -0
  30. package/dist/parser/parser.js +201 -0
  31. package/dist/parser/types.d.ts +68 -0
  32. package/dist/parser/types.js +5 -0
  33. package/dist/query/builder.d.ts +61 -0
  34. package/dist/query/builder.js +188 -0
  35. package/dist/query/index.d.ts +2 -0
  36. package/dist/query/index.js +18 -0
  37. package/dist/query/types.d.ts +79 -0
  38. package/dist/query/types.js +2 -0
  39. package/dist/security/index.d.ts +2 -0
  40. package/dist/security/index.js +18 -0
  41. package/dist/security/types.d.ts +181 -0
  42. package/dist/security/types.js +43 -0
  43. package/dist/security/validator.d.ts +191 -0
  44. package/dist/security/validator.js +344 -0
  45. package/dist/translators/drizzle/index.d.ts +73 -0
  46. package/dist/translators/drizzle/index.js +260 -0
  47. package/dist/translators/index.d.ts +8 -0
  48. package/dist/translators/index.js +27 -0
  49. package/dist/translators/sql/index.d.ts +108 -0
  50. package/dist/translators/sql/index.js +252 -0
  51. package/dist/translators/types.d.ts +39 -0
  52. package/dist/translators/types.js +8 -0
  53. package/examples/qk-next/README.md +35 -0
  54. package/examples/qk-next/app/favicon.ico +0 -0
  55. package/examples/qk-next/app/globals.css +122 -0
  56. package/examples/qk-next/app/layout.tsx +121 -0
  57. package/examples/qk-next/app/page.tsx +813 -0
  58. package/examples/qk-next/app/providers.tsx +80 -0
  59. package/examples/qk-next/components/aurora-background.tsx +12 -0
  60. package/examples/qk-next/components/github-stars.tsx +51 -0
  61. package/examples/qk-next/components/mode-toggle.tsx +27 -0
  62. package/examples/qk-next/components/reactbits/blocks/Backgrounds/Aurora/Aurora.tsx +217 -0
  63. package/examples/qk-next/components/reactbits/blocks/Backgrounds/LightRays/LightRays.tsx +474 -0
  64. package/examples/qk-next/components/theme-provider.tsx +11 -0
  65. package/examples/qk-next/components/ui/card.tsx +92 -0
  66. package/examples/qk-next/components/ui/command.tsx +184 -0
  67. package/examples/qk-next/components/ui/dialog.tsx +143 -0
  68. package/examples/qk-next/components/ui/drawer.tsx +135 -0
  69. package/examples/qk-next/components/ui/hover-card.tsx +44 -0
  70. package/examples/qk-next/components/ui/icons.tsx +148 -0
  71. package/examples/qk-next/components/ui/sonner.tsx +26 -0
  72. package/examples/qk-next/components/ui/table.tsx +117 -0
  73. package/examples/qk-next/components.json +21 -0
  74. package/examples/qk-next/eslint.config.mjs +21 -0
  75. package/examples/qk-next/jsrepo.json +13 -0
  76. package/examples/qk-next/lib/utils.ts +6 -0
  77. package/examples/qk-next/next.config.ts +8 -0
  78. package/examples/qk-next/package.json +48 -0
  79. package/examples/qk-next/pnpm-lock.yaml +5558 -0
  80. package/examples/qk-next/postcss.config.mjs +5 -0
  81. package/examples/qk-next/public/file.svg +1 -0
  82. package/examples/qk-next/public/globe.svg +1 -0
  83. package/examples/qk-next/public/next.svg +1 -0
  84. package/examples/qk-next/public/vercel.svg +1 -0
  85. package/examples/qk-next/public/window.svg +1 -0
  86. package/examples/qk-next/tsconfig.json +42 -0
  87. package/examples/qk-next/types/sonner.d.ts +3 -0
  88. package/jest.config.js +26 -0
  89. package/package.json +51 -0
  90. package/src/adapters/drizzle/drizzle-adapter.test.ts +115 -0
  91. package/src/adapters/drizzle/index.ts +299 -0
  92. package/src/adapters/index.ts +11 -0
  93. package/src/adapters/types.ts +72 -0
  94. package/src/index.ts +194 -0
  95. package/src/integration.test.ts +202 -0
  96. package/src/parser/index.ts +2 -0
  97. package/src/parser/parser.test.ts +1056 -0
  98. package/src/parser/parser.ts +268 -0
  99. package/src/parser/types.ts +97 -0
  100. package/src/query/builder.test.ts +272 -0
  101. package/src/query/builder.ts +274 -0
  102. package/src/query/index.ts +2 -0
  103. package/src/query/types.ts +107 -0
  104. package/src/security/index.ts +2 -0
  105. package/src/security/types.ts +210 -0
  106. package/src/security/validator.test.ts +459 -0
  107. package/src/security/validator.ts +395 -0
  108. package/src/security.test.ts +366 -0
  109. package/src/translators/drizzle/drizzle-translator.test.ts +128 -0
  110. package/src/translators/drizzle/index.test.ts +45 -0
  111. package/src/translators/drizzle/index.ts +346 -0
  112. package/src/translators/index.ts +14 -0
  113. package/src/translators/sql/index.test.ts +45 -0
  114. package/src/translators/sql/index.ts +331 -0
  115. package/src/translators/sql/sql-translator.test.ts +419 -0
  116. package/src/translators/types.ts +44 -0
  117. package/src/types/sonner.d.ts +3 -0
  118. package/tsconfig.json +34 -0
@@ -0,0 +1,438 @@
1
+ ---
2
+ description: the readme of liqe; a poor man's documentation for liqe
3
+ globs:
4
+ alwaysApply: false
5
+ ---
6
+ # liqe
7
+
8
+ [![Coveralls](https://img.shields.io/coveralls/gajus/liqe.svg?style=flat-square)](https://coveralls.io/github/gajus/liqe)
9
+ [![NPM version](http://img.shields.io/npm/v/liqe.svg?style=flat-square)](https://www.npmjs.org/package/liqe)
10
+ [![Canonical Code Style](https://img.shields.io/badge/code%20style-canonical-blue.svg?style=flat-square)](https://github.com/gajus/canonical)
11
+ [![Twitter Follow](https://img.shields.io/twitter/follow/kuizinas.svg?style=social&label=Follow)](https://twitter.com/kuizinas)
12
+
13
+ Lightweight and performant Lucene-like parser, serializer and search engine.
14
+
15
+ * [Motivation](#motivation)
16
+ * [Usage](#usage)
17
+ * [Query Syntax](#query-syntax)
18
+ * [Liqe syntax cheat sheet](#liqe-syntax-cheat-sheet)
19
+ * [Keyword matching](#keyword-matching)
20
+ * [Number matching](#number-matching)
21
+ * [Range matching](#range-matching)
22
+ * [Wildcard matching](#wildcard-matching)
23
+ * [Boolean operators](#boolean-operators)
24
+ * [Serializer](#serializer)
25
+ * [AST](#ast)
26
+ * [Utilities](#utilities)
27
+ * [Compatibility with Lucene](#compatibility-with-lucene)
28
+ * [Recipes](#recipes)
29
+ * [Handling syntax errors](#handling-syntax-errors)
30
+ * [Highlighting matches](#highlighting-matches)
31
+ * [Development](#development)
32
+ * [Tutorials](#tutorials)
33
+
34
+ ## Motivation
35
+
36
+ Originally built Liqe to enable [Roarr](https://github.com/gajus/roarr) log filtering via [cli](https://github.com/gajus/roarr-cli#filtering-logs). I have since been polishing this project as a hobby/intellectual exercise. I've seen it being adopted by [various](https://github.com/gajus/liqe/network/dependents) CLI and web applications that require advanced search. To my knowledge, it is currently the most complete Lucene-like syntax parser and serializer in JavaScript, as well as a compatible in-memory search engine.
37
+
38
+ Liqe use cases include:
39
+
40
+ * parsing search queries
41
+ * serializing parsed queries
42
+ * searching JSON documents using the Liqe query language (LQL)
43
+
44
+ Note that the [Liqe AST](#ast) is treated as a public API, i.e., one could implement their own search mechanism that uses Liqe query language (LQL).
45
+
46
+ ## Usage
47
+
48
+ ```ts
49
+ import {
50
+ filter,
51
+ highlight,
52
+ parse,
53
+ test,
54
+ } from 'liqe';
55
+
56
+ const persons = [
57
+ {
58
+ height: 180,
59
+ name: 'John Morton',
60
+ },
61
+ {
62
+ height: 175,
63
+ name: 'David Barker',
64
+ },
65
+ {
66
+ height: 170,
67
+ name: 'Thomas Castro',
68
+ },
69
+ ];
70
+ ```
71
+
72
+ Filter a collection:
73
+
74
+ ```ts
75
+ filter(parse('height:>170'), persons);
76
+ // [
77
+ // {
78
+ // height: 180,
79
+ // name: 'John Morton',
80
+ // },
81
+ // {
82
+ // height: 175,
83
+ // name: 'David Barker',
84
+ // },
85
+ // ]
86
+ ```
87
+
88
+ Test a single object:
89
+
90
+ ```ts
91
+ test(parse('name:John'), persons[0]);
92
+ // true
93
+ test(parse('name:David'), persons[0]);
94
+ // false
95
+ ```
96
+
97
+ Highlight matching fields and substrings:
98
+
99
+ ```ts
100
+ highlight(parse('name:john'), persons[0]);
101
+ // [
102
+ // {
103
+ // path: 'name',
104
+ // query: /(John)/,
105
+ // }
106
+ // ]
107
+ highlight(parse('height:180'), persons[0]);
108
+ // [
109
+ // {
110
+ // path: 'height',
111
+ // }
112
+ // ]
113
+ ```
114
+
115
+ ## Query Syntax
116
+
117
+ Liqe uses Liqe Query Language (LQL), which is heavily inspired by Lucene but extends it in various ways that allow a more powerful search experience.
118
+
119
+ ### Liqe syntax cheat sheet
120
+
121
+ ```rb
122
+ # search for "foo" term anywhere in the document (case insensitive)
123
+ foo
124
+
125
+ # search for "foo" term anywhere in the document (case sensitive)
126
+ 'foo'
127
+ "foo"
128
+
129
+ # search for "foo" term in `name` field
130
+ name:foo
131
+
132
+ # search for "foo" term in `full name` field
133
+ 'full name':foo
134
+ "full name":foo
135
+
136
+ # search for "foo" term in `first` field, member of `name`, i.e.
137
+ # matches {name: {first: 'foo'}}
138
+ name.first:foo
139
+
140
+ # search using regex
141
+ name:/foo/
142
+ name:/foo/o
143
+
144
+ # search using wildcard
145
+ name:foo*bar
146
+ name:foo?bar
147
+
148
+ # boolean search
149
+ member:true
150
+ member:false
151
+
152
+ # null search
153
+ member:null
154
+
155
+ # search for age =, >, >=, <, <=
156
+ height:=100
157
+ height:>100
158
+ height:>=100
159
+ height:<100
160
+ height:<=100
161
+
162
+ # search for height in range (inclusive, exclusive)
163
+ height:[100 TO 200]
164
+ height:{100 TO 200}
165
+
166
+ # boolean operators
167
+ name:foo AND height:=100
168
+ name:foo OR name:bar
169
+
170
+ # unary operators
171
+ NOT foo
172
+ -foo
173
+ NOT foo:bar
174
+ -foo:bar
175
+ name:foo AND NOT (bio:bar OR bio:baz)
176
+
177
+ # implicit AND boolean operator
178
+ name:foo height:=100
179
+
180
+ # grouping
181
+ name:foo AND (bio:bar OR bio:baz)
182
+ ```
183
+
184
+ ### Keyword matching
185
+
186
+ Search for word "foo" in any field (case insensitive).
187
+
188
+ ```rb
189
+ foo
190
+ ```
191
+
192
+ Search for word "foo" in the `name` field.
193
+
194
+ ```rb
195
+ name:foo
196
+ ```
197
+
198
+ Search for `name` field values matching `/foo/i` regex.
199
+
200
+ ```rb
201
+ name:/foo/i
202
+ ```
203
+
204
+ Search for `name` field values matching `f*o` wildcard pattern.
205
+
206
+ ```rb
207
+ name:f*o
208
+ ```
209
+
210
+ Search for `name` field values matching `f?o` wildcard pattern.
211
+
212
+ ```rb
213
+ name:f?o
214
+ ```
215
+
216
+ Search for phrase "foo bar" in the `name` field (case sensitive).
217
+
218
+ ```rb
219
+ name:"foo bar"
220
+ ```
221
+
222
+ ### Number matching
223
+
224
+ Search for value equal to 100 in the `height` field.
225
+
226
+ ```rb
227
+ height:=100
228
+ ```
229
+
230
+ Search for value greater than 100 in the `height` field.
231
+
232
+ ```rb
233
+ height:>100
234
+ ```
235
+
236
+ Search for value greater than or equal to 100 in the `height` field.
237
+
238
+ ```rb
239
+ height:>=100
240
+ ```
241
+
242
+ ### Range matching
243
+
244
+ Search for value greater or equal to 100 and lower or equal to 200 in the `height` field.
245
+
246
+ ```rb
247
+ height:[100 TO 200]
248
+ ```
249
+
250
+ Search for value greater than 100 and lower than 200 in the `height` field.
251
+
252
+ ```rb
253
+ height:{100 TO 200}
254
+ ```
255
+
256
+ ### Wildcard matching
257
+
258
+ Search for any word that starts with "foo" in the `name` field.
259
+
260
+ ```rb
261
+ name:foo*
262
+ ```
263
+
264
+ Search for any word that starts with "foo" and ends with "bar" in the `name` field.
265
+
266
+ ```rb
267
+ name:foo*bar
268
+ ```
269
+
270
+ Search for any word that starts with "foo" in the `name` field, followed by a single arbitrary character.
271
+
272
+ ```rb
273
+ name:foo?
274
+ ```
275
+
276
+ Search for any word that starts with "foo", followed by a single arbitrary character and immediately ends with "bar" in the `name` field.
277
+
278
+ ```rb
279
+ name:foo?bar
280
+ ```
281
+
282
+ ### Boolean operators
283
+
284
+ Search for phrase "foo bar" in the `name` field AND the phrase "quick fox" in the `bio` field.
285
+
286
+ ```rb
287
+ name:"foo bar" AND bio:"quick fox"
288
+ ```
289
+
290
+ Search for either the phrase "foo bar" in the `name` field AND the phrase "quick fox" in the `bio` field, or the word "fox" in the `name` field.
291
+
292
+ ```rb
293
+ (name:"foo bar" AND bio:"quick fox") OR name:fox
294
+ ```
295
+
296
+ ## Serializer
297
+
298
+ Serializer allows to convert Liqe tokens back to the original search query.
299
+
300
+ ```ts
301
+ import {
302
+ parse,
303
+ serialize,
304
+ } from 'liqe';
305
+
306
+ const tokens = parse('foo:bar');
307
+
308
+ // {
309
+ // expression: {
310
+ // location: {
311
+ // start: 4,
312
+ // },
313
+ // quoted: false,
314
+ // type: 'LiteralExpression',
315
+ // value: 'bar',
316
+ // },
317
+ // field: {
318
+ // location: {
319
+ // start: 0,
320
+ // },
321
+ // name: 'foo',
322
+ // path: ['foo'],
323
+ // quoted: false,
324
+ // type: 'Field',
325
+ // },
326
+ // location: {
327
+ // start: 0,
328
+ // },
329
+ // operator: {
330
+ // location: {
331
+ // start: 3,
332
+ // },
333
+ // operator: ':',
334
+ // type: 'ComparisonOperator',
335
+ // },
336
+ // type: 'Tag',
337
+ // }
338
+
339
+ serialize(tokens);
340
+ // 'foo:bar'
341
+ ```
342
+
343
+ ## AST
344
+
345
+ ```ts
346
+ import {
347
+ type BooleanOperatorToken,
348
+ type ComparisonOperatorToken,
349
+ type EmptyExpression,
350
+ type FieldToken,
351
+ type ImplicitBooleanOperatorToken,
352
+ type ImplicitFieldToken,
353
+ type LiteralExpressionToken,
354
+ type LogicalExpressionToken,
355
+ type RangeExpressionToken,
356
+ type RegexExpressionToken,
357
+ type TagToken,
358
+ type UnaryOperatorToken,
359
+ } from 'liqe';
360
+ ```
361
+
362
+ There are 11 AST tokens that describe a parsed Liqe query.
363
+
364
+ If you are building a serializer, then you must implement all of them for the complete coverage of all possible query inputs. Refer to the [built-in serializer](./src/serialize.ts) for an example.
365
+
366
+ ## Utilities
367
+
368
+ ```ts
369
+ import {
370
+ isSafeUnquotedExpression,
371
+ } from 'liqe';
372
+
373
+ /**
374
+ * Determines if an expression requires quotes.
375
+ * Use this if you need to programmatically manipulate the AST
376
+ * before using a serializer to convert the query back to text.
377
+ */
378
+ isSafeUnquotedExpression(expression: string): boolean;
379
+ ```
380
+
381
+ ## Compatibility with Lucene
382
+
383
+ The following Lucene abilities are not supported:
384
+
385
+ * [Fuzzy Searches](https://lucene.apache.org/core/2_9_4/queryparsersyntax.html#Fuzzy%20Searches)
386
+ * [Proximity Searches](https://lucene.apache.org/core/2_9_4/queryparsersyntax.html#Proximity%20Searches)
387
+ * [Boosting a Term](https://lucene.apache.org/core/2_9_4/queryparsersyntax.html#Boosting%20a%20Term)
388
+
389
+ ## Recipes
390
+
391
+ ### Handling syntax errors
392
+
393
+ In case of a syntax error, Liqe throws `SyntaxError`.
394
+
395
+ ```ts
396
+ import {
397
+ parse,
398
+ SyntaxError,
399
+ } from 'liqe';
400
+
401
+ try {
402
+ parse('foo bar');
403
+ } catch (error) {
404
+ if (error instanceof SyntaxError) {
405
+ console.error({
406
+ // Syntax error at line 1 column 5
407
+ message: error.message,
408
+ // 4
409
+ offset: error.offset,
410
+ // 1
411
+ offset: error.line,
412
+ // 5
413
+ offset: error.column,
414
+ });
415
+ } else {
416
+ throw error;
417
+ }
418
+ }
419
+ ```
420
+
421
+ ### Highlighting matches
422
+
423
+ Consider using [`highlight-words`](https://github.com/tricinel/highlight-words) package to highlight Liqe matches.
424
+
425
+ ## Development
426
+
427
+ ### Compiling Parser
428
+
429
+ If you are going to modify parser, then use `npm run watch` to run compiler in watch mode.
430
+
431
+ ### Benchmarking Changes
432
+
433
+ Before making any changes, capture the current benchmark on your machine using `npm run benchmark`. Run benchmark again after making any changes. Before committing changes, ensure that performance is not negatively impacted.
434
+
435
+
436
+ ## Tutorials
437
+
438
+ * [Building advanced SQL search from a user text input](https://contra.com/p/WobOBob7-building-advanced-sql-search-from-a-user-text-input)
@@ -0,0 +1,25 @@
1
+ // For format details, see https://aka.ms/devcontainer.json. For config options, see the
2
+ // README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu
3
+ {
4
+ "name": "Ubuntu",
5
+ // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
6
+ "image": "mcr.microsoft.com/devcontainers/base:jammy",
7
+ "features": {
8
+ "ghcr.io/devcontainers/features/node:1": {}
9
+ }
10
+
11
+ // Features to add to the dev container. More info: https://containers.dev/features.
12
+ // "features": {},
13
+
14
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
15
+ // "forwardPorts": [],
16
+
17
+ // Use 'postCreateCommand' to run commands after the container is created.
18
+ // "postCreateCommand": "uname -a",
19
+
20
+ // Configure tool-specific properties.
21
+ // "customizations": {},
22
+
23
+ // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
24
+ // "remoteUser": "root"
25
+ }
package/.eslintignore ADDED
@@ -0,0 +1 @@
1
+ examples/qk-next/components/**
package/.eslintrc.js ADDED
@@ -0,0 +1,39 @@
1
+ module.exports = {
2
+ root: true,
3
+ parser: '@typescript-eslint/parser',
4
+ plugins: ['@typescript-eslint'],
5
+ extends: [
6
+ 'eslint:recommended',
7
+ 'plugin:@typescript-eslint/recommended',
8
+ 'prettier'
9
+ ],
10
+ env: {
11
+ node: true,
12
+ jest: true
13
+ },
14
+ rules: {
15
+ '@typescript-eslint/explicit-function-return-type': 'error',
16
+ '@typescript-eslint/no-explicit-any': 'error',
17
+ '@typescript-eslint/no-unused-vars': 'error',
18
+ '@typescript-eslint/naming-convention': [
19
+ 'error',
20
+ {
21
+ selector: 'interface',
22
+ format: ['PascalCase'],
23
+ prefix: ['I']
24
+ },
25
+ {
26
+ selector: 'class',
27
+ format: ['PascalCase']
28
+ },
29
+ {
30
+ selector: 'typeAlias',
31
+ format: ['PascalCase']
32
+ },
33
+ {
34
+ selector: 'enum',
35
+ format: ['PascalCase']
36
+ }
37
+ ]
38
+ }
39
+ };
@@ -0,0 +1,12 @@
1
+ # To get started with Dependabot version updates, you'll need to specify which
2
+ # package ecosystems to update and where the package manifests are located.
3
+ # Please see the documentation for more information:
4
+ # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5
+ # https://containers.dev/guide/dependabot
6
+
7
+ version: 2
8
+ updates:
9
+ - package-ecosystem: "devcontainers"
10
+ directory: "/"
11
+ schedule:
12
+ interval: weekly
@@ -0,0 +1,114 @@
1
+ name: QueryKit CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main, staging]
6
+ pull_request:
7
+ branches: [main, staging]
8
+
9
+ jobs:
10
+ test:
11
+ name: Test & Lint
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - name: Checkout code
15
+ uses: actions/checkout@v4
16
+
17
+ - name: Setup Node.js
18
+ uses: actions/setup-node@v4
19
+ with:
20
+ node-version: '20'
21
+
22
+ - name: Setup pnpm
23
+ uses: pnpm/action-setup@v3
24
+ with:
25
+ version: 8
26
+ run_install: false
27
+
28
+ - name: Get pnpm store directory
29
+ id: pnpm-cache
30
+ shell: bash
31
+ run: |
32
+ echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
33
+
34
+ - name: Setup pnpm cache
35
+ uses: actions/cache@v4
36
+ with:
37
+ path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
38
+ key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
39
+ restore-keys: |
40
+ ${{ runner.os }}-pnpm-store-
41
+
42
+ - name: Install dependencies
43
+ run: pnpm i
44
+
45
+ - name: Lint code
46
+ run: pnpm run lint
47
+
48
+ - name: Check TypeScript compilation
49
+ run: pnpm exec tsc --noEmit
50
+
51
+ - name: Run tests
52
+ run: pnpm run test
53
+
54
+ build:
55
+ name: Build Package
56
+ needs: test
57
+ runs-on: ubuntu-latest
58
+ strategy:
59
+ matrix:
60
+ node-version: [18.x, 20.x]
61
+ steps:
62
+ - name: Checkout code
63
+ uses: actions/checkout@v4
64
+
65
+ - name: Setup Node.js ${{ matrix.node-version }}
66
+ uses: actions/setup-node@v4
67
+ with:
68
+ node-version: ${{ matrix.node-version }}
69
+
70
+ - name: Setup pnpm
71
+ uses: pnpm/action-setup@v3
72
+ with:
73
+ version: 8
74
+ run_install: false
75
+
76
+ - name: Get pnpm store directory
77
+ id: pnpm-cache
78
+ shell: bash
79
+ run: |
80
+ echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
81
+
82
+ - name: Setup pnpm cache
83
+ uses: actions/cache@v4
84
+ with:
85
+ path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
86
+ key: ${{ runner.os }}-pnpm-store-node-${{ matrix.node-version }}-${{ hashFiles('**/pnpm-lock.yaml') }}
87
+ restore-keys: |
88
+ ${{ runner.os }}-pnpm-store-node-${{ matrix.node-version }}-
89
+
90
+ - name: Install dependencies
91
+ run: pnpm install
92
+
93
+ - name: Build package
94
+ run: pnpm run build
95
+
96
+ - name: Create test package
97
+ run: pnpm pack
98
+
99
+ - name: Verify package exports
100
+ run: |
101
+ # Ensure package can be imported
102
+ mkdir -p test-import
103
+ cd test-import
104
+ echo '{ "type": "module" }' > package.json
105
+ echo 'import * as querykit from "../dist/index.js"; console.log("Package import successful!")' > test.js
106
+ node test.js
107
+
108
+ - name: Upload artifact
109
+ uses: actions/upload-artifact@v4
110
+ with:
111
+ name: querykit-package-${{ matrix.node-version }}
112
+ path: querykit-*.tgz
113
+
114
+
@@ -0,0 +1,61 @@
1
+ name: Publish public release
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ tags:
7
+ - 'v*'
8
+ workflow_dispatch:
9
+ inputs:
10
+ tag:
11
+ description: 'Tag name (e.g., v0.0.0)'
12
+ required: true
13
+ type: string
14
+
15
+ jobs:
16
+ publish-npm:
17
+ name: Publish package to npm registry
18
+ runs-on: ubuntu-latest
19
+ permissions:
20
+ contents: read
21
+ steps:
22
+ - name: Checkout code
23
+ uses: actions/checkout@v4
24
+ with:
25
+ ref: ${{ github.event.release.tag_name }}
26
+
27
+ - name: Setup Node.js
28
+ uses: actions/setup-node@v4
29
+ with:
30
+ node-version: '20'
31
+ registry-url: 'https://registry.npmjs.org'
32
+ always-auth: true
33
+
34
+ - name: Setup pnpm
35
+ uses: pnpm/action-setup@v3
36
+ with:
37
+ version: 8
38
+ run_install: false
39
+
40
+ - name: Install dependencies
41
+ run: pnpm install
42
+
43
+ - name: Build package
44
+ run: pnpm run build
45
+
46
+ - name: Verify tag is set
47
+ run: test -n "${TAG}" || { echo "TAG missing"; exit 1; }
48
+ env:
49
+ TAG: ${{ github.event.release.tag_name || inputs.tag || '' }}
50
+
51
+ - name: Verify tag matches package.json version
52
+ env:
53
+ TAG: ${{ github.event.release.tag_name || inputs.tag }}
54
+ run: node -e 'const v=require("./package.json").version; const tag=(process.env.TAG||"").replace(/^v/,""); if(v!==tag){console.error("package.json version "+v+" does not match tag "+tag); process.exit(1)} else {console.log("Version matches tag:", v)}'
55
+
56
+ - name: Publish package to npmjs
57
+ env:
58
+ NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
59
+ run: pnpm publish --no-git-checks --access public
60
+
61
+