kysely 0.27.5 → 0.27.6

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.
@@ -0,0 +1,183 @@
1
+ # ~~Contributor Covenant Code of Conduct~~ Kysely Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ ~~We as members, contributors, and leaders pledge to make participation in our~~
6
+ ~~community a harassment-free experience for everyone, regardless of age, body~~
7
+ ~~size, visible or invisible disability, ethnicity, sex characteristics, gender~~
8
+ ~~identity and expression, level of experience, education, socio-economic status,~~
9
+ ~~nationality, personal appearance, race, religion, or sexual identity~~
10
+ ~~and orientation.~~
11
+
12
+ ~~We pledge to act and interact in ways that contribute to an open, welcoming,~~
13
+ ~~diverse, inclusive, and healthy community.~~
14
+
15
+ Don't be an asshole.
16
+ You are not entitled to other people's time, energy, or attention.
17
+
18
+ That's it. That's the pledge.
19
+
20
+ ## Our Standards
21
+
22
+ ~~Examples of behavior that contributes to a positive environment for our~~
23
+ ~~community include:~~
24
+
25
+ ~~* Demonstrating empathy and kindness toward other people~~
26
+ ~~* Being respectful of differing opinions, viewpoints, and experiences~~
27
+ ~~* Giving and gracefully accepting constructive feedback~~
28
+ ~~* Accepting responsibility and apologizing to those affected by our mistakes,~~
29
+ ~~and learning from the experience~~
30
+ ~~* Focusing on what is best not just for us as individuals, but for the~~
31
+ ~~overall community~~
32
+
33
+ ~~Examples of unacceptable behavior include:~~
34
+
35
+ ~~* The use of sexualized language or imagery, and sexual attention or~~
36
+ ~~advances of any kind~~
37
+ ~~* Trolling, insulting or derogatory comments, and personal or political attacks~~
38
+ ~~* Public or private harassment~~
39
+ ~~* Publishing others' private information, such as a physical or email~~
40
+ ~~address, without their explicit permission~~
41
+ ~~* Other conduct which could reasonably be considered inappropriate in a~~
42
+ ~~professional setting~~
43
+
44
+ Don't be an asshole.
45
+ You are not entitled to other people's time, energy, or attention.
46
+
47
+ Examples of being an asshole or entitled include:
48
+
49
+ * Probably not the best idea to come in complaining and not having minimal respect to develop some basic sentences.
50
+ > what is the migration_lock ?? why we have this in the database - https://github.com/kysely-org/kysely/issues/973
51
+ * Probably not the best idea to attack the maintainer after he answered succinctly (out of character) for whatever reason (context switch? busy?).
52
+ > You got any more of them there examples buster? - https://github.com/kysely-org/kysely/issues/677#issuecomment-2019876074
53
+ * Probably not the best idea to come in all yelling and demanding stuff.
54
+ > NO documentation about error handling!!!!
55
+ > I am really confused that the docs doesnt have anything about error handling
56
+ > this is a mandataory things to add!! - https://github.com/kysely-org/kysely/issues/1217
57
+ * Probably not the best idea to tone police the maintainer after he spent time explaining library philosophy, and to publicly refuse to contribute something you demanded.
58
+ > I'm sorry fella but with your tone I really don't feel like it. Good luck - https://github.com/kysely-org/kysely/issues/709#issuecomment-1727617260
59
+ * Probably not the best idea to question the maintainer's decisions after properly answering your question, and to be entitled for JavaScript skill issue help after the issue is closed.
60
+ > I don't understand your answer and closing the issue. - https://github.com/kysely-org/kysely/issues/527#issuecomment-1577383794
61
+ * Probably not the best idea to jump into closed issues and be entitled to other people's efforts.
62
+ > why on earth there is no mention of this in the documentation? - https://github.com/kysely-org/kysely/issues/649#issuecomment-2430854835
63
+ * Probably not the best idea to not search existing issues, and then ask the maintainer to spend time doing it.
64
+ > could you please provide numbers of duplicated issues to explore the topic? - https://github.com/kysely-org/kysely/issues/762#issuecomment-1801951960
65
+ * Probably not the best idea to sarcastically suggest the maintainer is not helpful while using his hard work for free.
66
+ > Most helpful man in the world - https://github.com/kysely-org/kysely/issues/762#issuecomment-2404599281
67
+ * Probably not the best idea to side with the asshole/entitled by upvoting them or downvoting us.
68
+
69
+ ![image](https://github.com/user-attachments/assets/ca6efd57-d8db-428a-a274-7b45b380887f)
70
+ ![image](https://github.com/user-attachments/assets/62356fa3-7cea-4b82-9967-d56c76f0beee)
71
+
72
+
73
+
74
+ ## Enforcement Responsibilities
75
+
76
+ ~~Community leaders are responsible for clarifying and enforcing our standards of~~
77
+ ~~acceptable behavior and will take appropriate and fair corrective action in~~
78
+ ~~response to any behavior that they deem inappropriate, threatening, offensive,~~
79
+ ~~or harmful.~~
80
+
81
+ ~~Community leaders have the right and responsibility to remove, edit, or reject~~
82
+ ~~comments, commits, code, wiki edits, issues, and other contributions that are~~
83
+ ~~not aligned to this Code of Conduct, and will communicate reasons for moderation~~
84
+ ~~decisions when appropriate.~~
85
+
86
+ We are here in our free time. We are not getting paid to serve and please you.
87
+
88
+ We will not tolerate assholes or entitled behavior.
89
+
90
+ We are capable of banning you from the GitHub organization, Discord server, or any
91
+ other community space we manage. Don't make us do it.
92
+
93
+ ## Scope
94
+
95
+ ~~This Code of Conduct applies within all community spaces, and also applies when~~
96
+ ~~an individual is officially representing the community in public spaces.~~
97
+ ~~Examples of representing our community include using an official e-mail address,~~
98
+ ~~posting via an official social media account, or acting as an appointed~~
99
+ ~~representative at an online or offline event.~~
100
+
101
+ Relevant to all repositories in the Kysely GitHub organization, the Kysely Discord
102
+ server, and any other community space managed by Kysely.
103
+
104
+ ## Enforcement
105
+
106
+ ~~Instances of abusive, harassing, or otherwise unacceptable behavior may be~~
107
+ ~~reported to the community leaders responsible for enforcement at~~
108
+ ~~Kysely's GitHub repositories and Discord server.~~
109
+ ~~All complaints will be reviewed and investigated promptly and fairly.~~
110
+
111
+ ~~All community leaders are obligated to respect the privacy and security of the~~
112
+ ~~reporter of any incident.~~
113
+
114
+ Report to us anyone who is being an asshole or entitled.
115
+ We will take appropriate action if necessary.
116
+
117
+ ## Enforcement Guidelines
118
+
119
+ ~~Community leaders will follow these Community Impact Guidelines in determining~~
120
+ ~~the consequences for any action they deem in violation of this Code of Conduct:~~
121
+
122
+ We might do these things:
123
+
124
+ ~~### 1. Correction~~
125
+
126
+ **Community Impact**: ~~Use of inappropriate language or other behavior deemed~~
127
+ ~~unprofessional or unwelcome in the community.~~
128
+
129
+ Someone being an asshole or entitled.
130
+
131
+ **Consequence**: ~~A private, written warning from community leaders, providing~~
132
+ ~~clarity around the nature of the violation and an explanation of why the~~
133
+ ~~behavior was inappropriate. A public apology may be requested.~~
134
+
135
+ We might return the favor (we're not perfect), ignore you, edit/close/lock/delete
136
+ your post/comment, or ban you. There is no strike system.
137
+
138
+ ~~### 2. Warning~~
139
+
140
+ ~~**Community Impact**: A violation through a single incident or series~~
141
+ ~~of actions.~~
142
+
143
+ ~~**Consequence**: A warning with consequences for continued behavior. No~~
144
+ ~~interaction with the people involved, including unsolicited interaction with~~
145
+ ~~those enforcing the Code of Conduct, for a specified period of time. This~~
146
+ ~~includes avoiding interactions in community spaces as well as external channels~~
147
+ ~~like social media. Violating these terms may lead to a temporary or~~
148
+ ~~permanent ban.~~
149
+
150
+ ~~### 3. Temporary Ban~~
151
+
152
+ ~~**Community Impact**: A serious violation of community standards, including~~
153
+ ~~sustained inappropriate behavior.~~
154
+
155
+ ~~**Consequence**: A temporary ban from any sort of interaction or public~~
156
+ ~~communication with the community for a specified period of time. No public or~~
157
+ ~~private interaction with the people involved, including unsolicited interaction~~
158
+ ~~with those enforcing the Code of Conduct, is allowed during this period.~~
159
+ ~~Violating these terms may lead to a permanent ban.~~
160
+
161
+ ~~### 4. Permanent Ban~~
162
+
163
+ ~~**Community Impact**: Demonstrating a pattern of violation of community~~
164
+ ~~standards, including sustained inappropriate behavior, harassment of an~~
165
+ ~~individual, or aggression toward or disparagement of classes of individuals.~~
166
+
167
+ ~~**Consequence**: A permanent ban from any sort of public interaction within~~
168
+ ~~the community.~~
169
+
170
+ ## ~~Attribution~~
171
+
172
+ ~~This Code of Conduct is adapted from the [Contributor Covenant][homepage],~~
173
+ ~~version 2.0, available at~~
174
+ ~~https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.~~
175
+
176
+ ~~Community Impact Guidelines were inspired by [Mozilla's code of conduct~~
177
+ ~~enforcement ladder](https://github.com/mozilla/diversity).~~
178
+
179
+ ~~[homepage]: https://www.contributor-covenant.org~~
180
+
181
+ ~~For answers to common questions about this code of conduct, see the FAQ at~~
182
+ ~~https://www.contributor-covenant.org/faq. Translations are available at~~
183
+ ~~https://www.contributor-covenant.org/translations.~~
package/CONTRIBUTING.md CHANGED
@@ -1,17 +1,115 @@
1
- Would love to contribute to Kysely?
1
+ # Contribution Guidelines
2
2
 
3
- Awesome! We are not very organized yet. We don't have a plan or a roadmap.
3
+ Wanna contribute to Kysely or other libraries within the [kysely-org](https://github.com/kysely-org) organization?
4
4
 
5
- Probably the best way to get going is to watch the issue list.
6
- An issue with a `greenlit` label, has been acknowledged by the team and is ready for development.
7
- Find such an issue you'd like to tackle and ask to be assigned to it in the comment section.
8
- Feel free to open an issue if you found a bug or a missing feature you'd like to work on,
9
- and there aren't any issues mentioning it.
5
+ You're awesome! 🤗
10
6
 
11
- If you start working on something larger, it's a good idea to create a draft (WIP) PR at
12
- an early stage so that we can guide you to the right direction.
7
+ ## 😕 How Can I Contribute?
13
8
 
14
- There are quite a few guidelines to keep in mind:
9
+ ## 📢 By Spreading the Word
10
+
11
+ If you like what we're building here, please:
12
+
13
+ 1. Tell your friends, co-workers, family, neighbors, followers, etc.
14
+ 1. Speak about it in meetups and conferences. (if you need help with the presentation, please ask on Discord!).
15
+
16
+ There is no company behind this organization and project. We do this in our free time and cannot advertise it
17
+ all by ourselves. Companies have dedicated devrel teams doing all that work, other projects might have community
18
+ managers or full-time developers with the capacity to also handle social media and meetups with higher frequency.
19
+
20
+ More people using Kysely eventually means:
21
+
22
+ 1. less bugs left unreported and unsolved.
23
+ 1. high chance people share workarounds.
24
+ 1. more data points for innovation.
25
+ 1. more people who might contribute and push this thing even further.
26
+ 1. it becomes a standard part of the Node.js/Bun/Deno developer's toolbox so you'll happily stumble upon it
27
+ in future codebases you'll join, or it'll be easier to get buy-in from co-workers/leadership.
28
+
29
+ ## 🤝 By Helping People in Issues or on Discord
30
+
31
+ We need all the help we can get with supporting the community, answering questions, triaging!
32
+
33
+ If you've been using the library for a while, or know a thing or two about SQL, and specific databases, don't
34
+ be shy about it!
35
+
36
+ ## 🐛 By Submitting Issues (Bugs, Enhancement Ideas, Questions) or Opening threads on Discord
37
+
38
+ Please use search and make sure an existing **open** issue or thread doesn't exist before submitting. If an existing
39
+ issue is only somewhat relevant, please submit a new issue and reference the old one instead of commenting
40
+ on the existing one. Let us label your new issue as `duplicate` if it is the same thing - it's fine! If it's not
41
+ the same thing and you commented on another issue, it makes it harder to track on our end.
42
+
43
+ If possible/relevant, please provide a [playground link](https://kyse.link), Stackblitz OR a public git repository that
44
+ reproduces the issue.
45
+
46
+ Please provide the exact error/warning texts you're getting. "this doesn't work" or "this throws an error"
47
+ are not helping us help you.
48
+
49
+ Don't be an asshole. Don't demand support/service from us.
50
+
51
+ ## ⌨️ With Code!
52
+
53
+ ### 📘 Documentation Code Contributions
54
+
55
+ Pull requests are always welcome!
56
+
57
+ The [kysely.dev](https://kysely.dev) application is located @ [/site](https://github.com/kysely-org/kysely/tree/master/site).
58
+
59
+ It is a pretty standard [docusaurus](https://docusaurus.io/) application.
60
+
61
+ The code examples are extracted, using a custom script (see `"script:generate-site-examples"` @ [package.json](https://github.com/kysely-org/kysely/blob/master/package.json)),
62
+ and custom annotations, from [JSDocs](https://jsdoc.app/) comments in the source code @ [/src](https://github.com/kysely-org/kysely/tree/master/src).
63
+ If you need to change an existing code example, please do so in the source code AND then run the script.
64
+ _TODO: explain how to add a new code example._
65
+
66
+ If it is a big change (lots of lines of code OR files involved), please get a conversation going on an issue or on
67
+ Discord before starting work on it.
68
+
69
+ ### 🧙 Implementation Code Contributions (Bugfixes, Enhancements [Existing or New Features])
70
+
71
+ Pull requests (PRs) are welcome, BUT since:
72
+
73
+ 1. Our time and capacity as maintainers are limited.
74
+ 1. Your time is limited.
75
+ 1. Your motivation and morale is important to us.
76
+ 1. Onboarding in a new highly opinionated open-source project can be challenging.
77
+
78
+ We need a process in place:
79
+
80
+ 1. To make sure only things that have a good chance of being accepted are worked on. We hate saying "no".
81
+ 1. To make sure that things that are being worked on don't result in too much back-and-forth between authors and maintainers.
82
+ We hate seeing these things drag on for a long time, and we know how frustrating it is on your end.
83
+ 1. To make sure there's no pressure on you when you take on a task.
84
+ 1. To make sure there's a clear understanding of who is working on what to avoid redundancies and conflicts.
85
+
86
+ #### The Process
87
+
88
+ Here is the gist of it:
89
+
90
+ 1. If an issue on the subject **doesn't exist** yet, **submit** one. If you want to work on it, **ask** to be **assigned**
91
+ to it in the issue **description**, in the **comments**, OR on **Discord**.
92
+ 1. If an issue on the subject **exists** and is labeled with `bug` OR (`enhancement` AND `greenlit`\* OR even `good first issue`\**):
93
+ 1. If there is a pull request (**PR**) **linked** to it:
94
+ 1. If the **PR** is **stale** (a few months without new commits or comments from the PR author OR Kysely maintainers),
95
+ **ask** to be **assigned** to it and continue work on the PR.
96
+ 1. If the **PR** is **not stale**, you can still offer some review comments, or **ask** to **pair** up with the PR author.
97
+ 1. If there is **no PR linked** to it and a person is **already assigned** to it:
98
+ 1. If the assignment is stale (a few weeks without a PR opened after the person was assigned to the issue), ask
99
+ to be assigned to the issue in the comments OR on Discord.
100
+ 1. If the assignment is not stale, it's OK to ask the assignee if they'll be willing to pair up.
101
+ 1. If there is no PR linked to it and no assignee, ask to be assigned to it in the comments OR on Discord.
102
+
103
+ _\* an issue that was reviewed by the maintainers and would be nice to get a pull request for from the community._
104
+
105
+ _\** an issue that was reviewed by the maintainers and would be a nice opportunity to onboard an open-source newbie
106
+ into the codebase._
107
+
108
+ Once you're assigned to an issue, you can start working on your code changes. It's best to ask questions
109
+ in the issue or on Discord as early as possible, and even share your progress with the maintainers and community via a
110
+ draft (WIP) pull request.
111
+
112
+ #### Style/Design Philosophy
15
113
 
16
114
  * Kysely should have zero dependencies.
17
115
 
@@ -40,31 +138,31 @@ for all cases, are best left for consumers to implement.
40
138
 
41
139
  * Most features should have escape hatches.
42
140
 
43
- # Getting started
141
+ #### Getting Started
44
142
 
45
143
  1. fork kysely.
46
144
 
47
- 2. clone your fork.
145
+ 1. clone your fork.
48
146
 
49
- 3. install node.js (preferable latest LTS).
147
+ 1. install node.js (preferably the latest even-numbered version).
50
148
 
51
- 4. run `npm i` in your terminal to install dev dependencies.
149
+ 1. run `npm i` in your terminal to install dependencies.
52
150
 
53
- 5. create a branch.
151
+ 1. create a branch (we don't care about naming).
54
152
 
55
- 6. create a draft pull request. link the relevant issue by referring to it in the
153
+ 1. create a draft pull request. link the relevant issue by referring to it in the
56
154
  PR's description. E.g. `closes #123` will link the PR to issue/pull request #123.
57
155
 
58
- 7. implement your changes.
156
+ 1. implement your changes.
59
157
 
60
- # Testing
158
+ #### Testing
61
159
 
62
- 1. write functionality tests in `test/node`.
160
+ 1. write functionality tests @ [/test/node](https://github.com/kysely-org/kysely/tree/master/test/node).
63
161
 
64
- 2. write typings tests in `test/typings/test-d`
162
+ 1. write typings tests @ [/test/typings](https://github.com/kysely-org/kysely/tree/master/test/typings)
65
163
 
66
- 3. install docker.
164
+ 1. install docker.
67
165
 
68
- 4. run `docker-compose up` in your terminal to spin up database instances.
166
+ 1. run `docker compose up` in your terminal to spin up database instances.
69
167
 
70
- 5. run `npm test` in your terminal to run tests.
168
+ 1. run `npm test` in another terminal to run tests.
package/FUNDING.md ADDED
@@ -0,0 +1,25 @@
1
+ # Funding
2
+
3
+ Kysely is an MIT-licensed open-source project and is completely free to use - and will remain so.
4
+
5
+ Kysely is a labor of love. We do this in our free time.
6
+
7
+ We do not accept donations.
8
+
9
+ - Getting paid for open-source won't make us put more time and effort into open-source.
10
+ We have family, friends, and other hobbies and interests.
11
+
12
+ - We work in the tech industry, full-time, as software developers. We are comfortable as-is.
13
+
14
+ - We don't believe in freelance open-source as a stable source of income for the long term.
15
+ We have mouths to feed, and careers to maintain.
16
+
17
+ - Sponsored projects tend to be affected by big sponsors. We want to keep Kysely away
18
+ from companies' and VCs' opinions and business needs.
19
+
20
+ - Kysely is escapism for us. We don't want it to feel like another job.
21
+
22
+
23
+ Instead of donating to us, we encourage you to support meaningful causes or other open-source projects in need of funding.
24
+
25
+ Thank you for supporting Kysely through your usage, contributions, and feedback.
package/README.md CHANGED
@@ -1,13 +1,24 @@
1
1
  [![Stand With Ukraine](https://raw.githubusercontent.com/vshymanskyy/StandWithUkraine/main/banner2-direct.svg)](https://stand-with-ukraine.pp.ua)
2
2
 
3
- [![Discord](https://img.shields.io/discord/890118421587578920?logo=discord&style=flat)](https://discord.gg/xyBJ3GwvAm)
3
+ [![NPM Version](https://img.shields.io/npm/v/kysely?style=flat&label=latest)](https://github.com/kysely-org/kysely/releases/latest)
4
4
  [![Tests](https://github.com/kysely-org/kysely/actions/workflows/test.yml/badge.svg)](https://github.com/kysely-org/kysely)
5
5
  [![License](https://img.shields.io/github/license/kysely-org/kysely?style=flat)](https://github.com/kysely-org/kysely/blob/master/LICENSE)
6
6
  [![Issues](https://img.shields.io/github/issues-closed/kysely-org/kysely?logo=github)](https://github.com/kysely-org/kysely/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc)
7
7
  [![Pull Requests](https://img.shields.io/github/issues-pr-closed/kysely-org/kysely?label=PRs&logo=github&style=flat)](https://github.com/kysely-org/kysely/pulls?q=is%3Apr+is%3Aopen+sort%3Aupdated-desc)
8
+ ![GitHub contributors](https://img.shields.io/github/contributors/kysely-org/kysely)
8
9
  [![Downloads](https://img.shields.io/npm/dw/kysely?logo=npm)](https://www.npmjs.com/package/kysely)
9
10
  [![Bundle Size](https://img.shields.io/bundlephobia/minzip/kysely?label=size&logo=npm)](https://bundlephobia.com/package/kysely)
10
- [![Social](https://img.shields.io/twitter/follow/kysely_?style=social)](https://twitter.com/kysely_)
11
+
12
+ ###### Join the discussion ⠀⠀⠀⠀⠀⠀⠀
13
+ [![Discord](https://img.shields.io/badge/Discord-%235865F2.svg?style=flat&logo=discord&logoColor=white)](https://discord.gg/xyBJ3GwvAm)
14
+ [![Bluesky](https://img.shields.io/badge/Bluesky-0285FF?style=flat&logo=Bluesky&logoColor=white)](https://bsky.app/profile/kysely.dev)
15
+
16
+ ###### Get started
17
+ [![Postgres](https://img.shields.io/badge/postgres-%23316192.svg?style=flat&logo=postgresql&logoColor=white)](https://kysely.dev/docs/getting-started?dialect=postgresql)
18
+ [![MySQL](https://img.shields.io/badge/mysql-4479A1.svg?style=flat&logo=mysql&logoColor=white)](https://kysely.dev/docs/getting-started?dialect=mysql)
19
+ [![MicrosoftSQLServer](https://img.shields.io/badge/Microsoft%20SQL%20Server-CC2927?style=flat&logo=microsoft%20sql%20server&logoColor=white)](https://kysely.dev/docs/getting-started?dialect=mssql)
20
+ [![SQLite](https://img.shields.io/badge/sqlite-%2307405e.svg?style=flat&logo=sqlite&logoColor=white)](https://kysely.dev/docs/getting-started?dialect=sqlite)
21
+ & more!
11
22
 
12
23
  # [Kysely](https://kysely.dev)
13
24
 
@@ -13,59 +13,81 @@ class SqliteIntrospector {
13
13
  return [];
14
14
  }
15
15
  async getTables(options = { withInternalKyselyTables: false }) {
16
- let query = this.#db
16
+ return await this.#getTableMetadata(options);
17
+ }
18
+ async getMetadata(options) {
19
+ return {
20
+ tables: await this.getTables(options),
21
+ };
22
+ }
23
+ #tablesQuery(qb, options) {
24
+ let tablesQuery = qb
17
25
  .selectFrom('sqlite_master')
18
26
  .where('type', 'in', ['table', 'view'])
19
27
  .where('name', 'not like', 'sqlite_%')
20
- .select('name')
21
- .orderBy('name')
22
- .$castTo();
28
+ .select(['name', 'sql', 'type'])
29
+ .orderBy('name');
23
30
  if (!options.withInternalKyselyTables) {
24
- query = query
31
+ tablesQuery = tablesQuery
25
32
  .where('name', '!=', migrator_js_1.DEFAULT_MIGRATION_TABLE)
26
33
  .where('name', '!=', migrator_js_1.DEFAULT_MIGRATION_LOCK_TABLE);
27
34
  }
28
- const tables = await query.execute();
29
- return Promise.all(tables.map(({ name }) => this.#getTableMetadata(name)));
35
+ return tablesQuery;
30
36
  }
31
- async getMetadata(options) {
32
- return {
33
- tables: await this.getTables(options),
34
- };
35
- }
36
- async #getTableMetadata(table) {
37
- const db = this.#db;
38
- // Get the SQL that was used to create the table.
39
- const tableDefinition = await db
40
- .selectFrom('sqlite_master')
41
- .where('name', '=', table)
42
- .select(['sql', 'type'])
43
- .$castTo()
44
- .executeTakeFirstOrThrow();
45
- // Try to find the name of the column that has `autoincrement` 🤦
46
- const autoIncrementCol = tableDefinition.sql
47
- ?.split(/[\(\),]/)
48
- ?.find((it) => it.toLowerCase().includes('autoincrement'))
49
- ?.trimStart()
50
- ?.split(/\s+/)?.[0]
51
- ?.replace(/["`]/g, '');
52
- const columns = await db
53
- .selectFrom((0, sql_js_1.sql) `pragma_table_info(${table})`.as('table_info'))
54
- .select(['name', 'type', 'notnull', 'dflt_value'])
55
- .orderBy('cid')
37
+ async #getTableMetadata(options) {
38
+ const tablesResult = await this.#tablesQuery(this.#db, options).execute();
39
+ const tableMetadata = await this.#db
40
+ .with('table_list', (qb) => this.#tablesQuery(qb, options))
41
+ .selectFrom([
42
+ 'table_list as tl',
43
+ (0, sql_js_1.sql) `pragma_table_info(tl.name)`.as('p'),
44
+ ])
45
+ .select([
46
+ 'tl.name as table',
47
+ 'p.cid',
48
+ 'p.name',
49
+ 'p.type',
50
+ 'p.notnull',
51
+ 'p.dflt_value',
52
+ 'p.pk',
53
+ ])
54
+ .orderBy(['tl.name', 'p.cid'])
56
55
  .execute();
57
- return {
58
- name: table,
59
- isView: tableDefinition.type === 'view',
60
- columns: columns.map((col) => ({
61
- name: col.name,
62
- dataType: col.type,
63
- isNullable: !col.notnull,
64
- isAutoIncrementing: col.name === autoIncrementCol,
65
- hasDefaultValue: col.dflt_value != null,
66
- comment: undefined,
67
- })),
68
- };
56
+ const columnsByTable = {};
57
+ for (const row of tableMetadata) {
58
+ columnsByTable[row.table] ??= [];
59
+ columnsByTable[row.table].push(row);
60
+ }
61
+ return tablesResult.map(({ name, sql, type }) => {
62
+ // // Try to find the name of the column that has `autoincrement` 🤦
63
+ let autoIncrementCol = sql
64
+ ?.split(/[\(\),]/)
65
+ ?.find((it) => it.toLowerCase().includes('autoincrement'))
66
+ ?.trimStart()
67
+ ?.split(/\s+/)?.[0]
68
+ ?.replace(/["`]/g, '');
69
+ const columns = columnsByTable[name] ?? [];
70
+ // Otherwise, check for an INTEGER PRIMARY KEY
71
+ // https://www.sqlite.org/autoinc.html
72
+ if (!autoIncrementCol) {
73
+ const pkCols = columns.filter((r) => r.pk > 0);
74
+ if (pkCols.length === 1 && pkCols[0].type.toLowerCase() === 'integer') {
75
+ autoIncrementCol = pkCols[0].name;
76
+ }
77
+ }
78
+ return {
79
+ name: name,
80
+ isView: type === 'view',
81
+ columns: columns.map((col) => ({
82
+ name: col.name,
83
+ dataType: col.type,
84
+ isNullable: !col.notnull,
85
+ isAutoIncrementing: col.name === autoIncrementCol,
86
+ hasDefaultValue: col.dflt_value != null,
87
+ comment: undefined,
88
+ })),
89
+ };
90
+ });
69
91
  }
70
92
  }
71
93
  exports.SqliteIntrospector = SqliteIntrospector;
@@ -444,6 +444,8 @@ export interface ExpressionBuilder<DB, TB extends keyof DB> {
444
444
  * inner join "pet" on "pet"."owner_id" = "person"."id"
445
445
  * group by "person"."id"
446
446
  * ```
447
+ *
448
+ * If you need a column reference, use {@link ref}.
447
449
  */
448
450
  table<T extends TB & string>(table: T): ExpressionWrapper<DB, TB, Selectable<DB[T]>>;
449
451
  /**
@@ -271,7 +271,7 @@ export interface MigratorProps {
271
271
  *
272
272
  * When false, migrations must be run in their exact alpha-numeric order.
273
273
  * This is checked against the migrations already run in the database
274
- * (`migrationTableName'). This ensures your migrations are always run in
274
+ * (`migrationTableName`). This ensures your migrations are always run in
275
275
  * the same order and is the safest option.
276
276
  *
277
277
  * When true, migrations are still run in alpha-numeric order, but
@@ -280,6 +280,13 @@ export interface MigratorProps {
280
280
  * order.
281
281
  */
282
282
  readonly allowUnorderedMigrations?: boolean;
283
+ /**
284
+ * A function that compares migration names, used when sorting migrations in
285
+ * ascending order.
286
+ *
287
+ * Default is `name0.localeCompare(name1)`.
288
+ */
289
+ readonly nameComparator?: (name0: string, name1: string) => number;
283
290
  }
284
291
  /**
285
292
  * All migration methods ({@link Migrator.migrateTo | migrateTo},
@@ -63,6 +63,7 @@ class Migrator {
63
63
  .withPlugin(this.#schemaPlugin)
64
64
  .selectFrom(this.#migrationTable)
65
65
  .select(['name', 'timestamp'])
66
+ .$narrowType()
66
67
  .execute()
67
68
  : [];
68
69
  const migrations = await this.#resolveMigrations();
@@ -465,10 +466,19 @@ class Migrator {
465
466
  const executedMigrations = await db
466
467
  .withPlugin(this.#schemaPlugin)
467
468
  .selectFrom(this.#migrationTable)
468
- .select('name')
469
- .orderBy(['timestamp', 'name'])
469
+ .select(['name', 'timestamp'])
470
+ .$narrowType()
470
471
  .execute();
471
- return executedMigrations.map((it) => it.name);
472
+ const nameComparator = this.#props.nameComparator || ((a, b) => a.localeCompare(b));
473
+ return (executedMigrations
474
+ // https://github.com/kysely-org/kysely/issues/843
475
+ .sort((a, b) => {
476
+ if (a.timestamp === b.timestamp) {
477
+ return nameComparator(a.name, b.name);
478
+ }
479
+ return (new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
480
+ })
481
+ .map((it) => it.name));
472
482
  }
473
483
  #ensureNoMissingMigrations(migrations, executedMigrations) {
474
484
  // Ensure all executed migrations exist in the `migrations` list.
@@ -42,9 +42,11 @@ function parseRowValues(row, columns) {
42
42
  length: columns.size,
43
43
  });
44
44
  let hasUndefinedOrComplexColumns = false;
45
+ let indexedRowColumns = rowColumns.length;
45
46
  for (const col of rowColumns) {
46
47
  const columnIdx = columns.get(col);
47
48
  if ((0, object_utils_js_1.isUndefined)(columnIdx)) {
49
+ indexedRowColumns--;
48
50
  continue;
49
51
  }
50
52
  const value = row[col];
@@ -53,7 +55,7 @@ function parseRowValues(row, columns) {
53
55
  }
54
56
  rowValues[columnIdx] = value;
55
57
  }
56
- const hasMissingColumns = rowColumns.length < columns.size;
58
+ const hasMissingColumns = indexedRowColumns < columns.size;
57
59
  if (hasMissingColumns || hasUndefinedOrComplexColumns) {
58
60
  const defaultValue = default_insert_value_node_js_1.DefaultInsertValueNode.create();
59
61
  return value_list_node_js_1.ValueListNode.create(rowValues.map((it) => (0, object_utils_js_1.isUndefined)(it) ? defaultValue : (0, value_parser_js_1.parseValueExpression)(it)));
@@ -23,7 +23,10 @@ function createSnakeCaseMapper({ upperCase = false, underscoreBeforeDigits = fal
23
23
  // If underScoreBeforeDigits is true then, well, insert an underscore
24
24
  // before digits :). Only the first digit gets an underscore if
25
25
  // there are multiple.
26
- if (underscoreBeforeDigits && isDigit(char) && !isDigit(prevChar)) {
26
+ if (underscoreBeforeDigits &&
27
+ isDigit(char) &&
28
+ !isDigit(prevChar) &&
29
+ !out.endsWith('_')) {
27
30
  out += '_' + char;
28
31
  continue;
29
32
  }
@@ -1,3 +1,5 @@
1
+ import { AggregateFunctionNode } from '../../operation-node/aggregate-function-node.js';
2
+ import { FunctionNode } from '../../operation-node/function-node.js';
1
3
  import { OperationNodeTransformer } from '../../operation-node/operation-node-transformer.js';
2
4
  import { OperationNode } from '../../operation-node/operation-node.js';
3
5
  import { ReferencesNode } from '../../operation-node/references-node.js';
@@ -8,4 +10,6 @@ export declare class WithSchemaTransformer extends OperationNodeTransformer {
8
10
  protected transformNodeImpl<T extends OperationNode>(node: T): T;
9
11
  protected transformSchemableIdentifier(node: SchemableIdentifierNode): SchemableIdentifierNode;
10
12
  protected transformReferences(node: ReferencesNode): ReferencesNode;
13
+ protected transformAggregateFunction(node: AggregateFunctionNode): AggregateFunctionNode;
14
+ protected transformFunction(node: FunctionNode): FunctionNode;
11
15
  }
@@ -31,6 +31,10 @@ const ROOT_OPERATION_NODES = (0, object_utils_js_1.freeze)({
31
31
  UpdateQueryNode: true,
32
32
  MergeQueryNode: true,
33
33
  });
34
+ const SCHEMALESS_FUNCTIONS = {
35
+ json_agg: true,
36
+ to_json: true,
37
+ };
34
38
  class WithSchemaTransformer extends operation_node_transformer_js_1.OperationNodeTransformer {
35
39
  #schema;
36
40
  #schemableIds = new Set();
@@ -80,6 +84,28 @@ class WithSchemaTransformer extends operation_node_transformer_js_1.OperationNod
80
84
  table: table_node_js_1.TableNode.createWithSchema(this.#schema, transformed.table.table.identifier.name),
81
85
  };
82
86
  }
87
+ transformAggregateFunction(node) {
88
+ return {
89
+ ...super.transformAggregateFunction({ ...node, aggregated: [] }),
90
+ aggregated: this.#transformTableArgsWithoutSchemas(node, 'aggregated'),
91
+ };
92
+ }
93
+ transformFunction(node) {
94
+ return {
95
+ ...super.transformFunction({ ...node, arguments: [] }),
96
+ arguments: this.#transformTableArgsWithoutSchemas(node, 'arguments'),
97
+ };
98
+ }
99
+ #transformTableArgsWithoutSchemas(node, argsKey) {
100
+ return SCHEMALESS_FUNCTIONS[node.func]
101
+ ? node[argsKey].map((arg) => !table_node_js_1.TableNode.is(arg) || arg.table.schema
102
+ ? this.transformNode(arg)
103
+ : {
104
+ ...arg,
105
+ table: this.transformIdentifier(arg.table.identifier),
106
+ })
107
+ : this.transformNodeList(node[argsKey]);
108
+ }
83
109
  #isRootOperationNode(node) {
84
110
  return node.kind in ROOT_OPERATION_NODES;
85
111
  }
@@ -199,7 +199,7 @@ export interface Sql {
199
199
  * select first_name from "person"
200
200
  * ```
201
201
  *
202
- * The refefences can also include a schema on supported databases:
202
+ * The references can also include a schema on supported databases:
203
203
  *
204
204
  * ```ts
205
205
  * const table = 'public.person'
@@ -11,58 +11,80 @@ export class SqliteIntrospector {
11
11
  return [];
12
12
  }
13
13
  async getTables(options = { withInternalKyselyTables: false }) {
14
- let query = this.#db
14
+ return await this.#getTableMetadata(options);
15
+ }
16
+ async getMetadata(options) {
17
+ return {
18
+ tables: await this.getTables(options),
19
+ };
20
+ }
21
+ #tablesQuery(qb, options) {
22
+ let tablesQuery = qb
15
23
  .selectFrom('sqlite_master')
16
24
  .where('type', 'in', ['table', 'view'])
17
25
  .where('name', 'not like', 'sqlite_%')
18
- .select('name')
19
- .orderBy('name')
20
- .$castTo();
26
+ .select(['name', 'sql', 'type'])
27
+ .orderBy('name');
21
28
  if (!options.withInternalKyselyTables) {
22
- query = query
29
+ tablesQuery = tablesQuery
23
30
  .where('name', '!=', DEFAULT_MIGRATION_TABLE)
24
31
  .where('name', '!=', DEFAULT_MIGRATION_LOCK_TABLE);
25
32
  }
26
- const tables = await query.execute();
27
- return Promise.all(tables.map(({ name }) => this.#getTableMetadata(name)));
33
+ return tablesQuery;
28
34
  }
29
- async getMetadata(options) {
30
- return {
31
- tables: await this.getTables(options),
32
- };
33
- }
34
- async #getTableMetadata(table) {
35
- const db = this.#db;
36
- // Get the SQL that was used to create the table.
37
- const tableDefinition = await db
38
- .selectFrom('sqlite_master')
39
- .where('name', '=', table)
40
- .select(['sql', 'type'])
41
- .$castTo()
42
- .executeTakeFirstOrThrow();
43
- // Try to find the name of the column that has `autoincrement` 🤦
44
- const autoIncrementCol = tableDefinition.sql
45
- ?.split(/[\(\),]/)
46
- ?.find((it) => it.toLowerCase().includes('autoincrement'))
47
- ?.trimStart()
48
- ?.split(/\s+/)?.[0]
49
- ?.replace(/["`]/g, '');
50
- const columns = await db
51
- .selectFrom(sql `pragma_table_info(${table})`.as('table_info'))
52
- .select(['name', 'type', 'notnull', 'dflt_value'])
53
- .orderBy('cid')
35
+ async #getTableMetadata(options) {
36
+ const tablesResult = await this.#tablesQuery(this.#db, options).execute();
37
+ const tableMetadata = await this.#db
38
+ .with('table_list', (qb) => this.#tablesQuery(qb, options))
39
+ .selectFrom([
40
+ 'table_list as tl',
41
+ sql `pragma_table_info(tl.name)`.as('p'),
42
+ ])
43
+ .select([
44
+ 'tl.name as table',
45
+ 'p.cid',
46
+ 'p.name',
47
+ 'p.type',
48
+ 'p.notnull',
49
+ 'p.dflt_value',
50
+ 'p.pk',
51
+ ])
52
+ .orderBy(['tl.name', 'p.cid'])
54
53
  .execute();
55
- return {
56
- name: table,
57
- isView: tableDefinition.type === 'view',
58
- columns: columns.map((col) => ({
59
- name: col.name,
60
- dataType: col.type,
61
- isNullable: !col.notnull,
62
- isAutoIncrementing: col.name === autoIncrementCol,
63
- hasDefaultValue: col.dflt_value != null,
64
- comment: undefined,
65
- })),
66
- };
54
+ const columnsByTable = {};
55
+ for (const row of tableMetadata) {
56
+ columnsByTable[row.table] ??= [];
57
+ columnsByTable[row.table].push(row);
58
+ }
59
+ return tablesResult.map(({ name, sql, type }) => {
60
+ // // Try to find the name of the column that has `autoincrement` 🤦
61
+ let autoIncrementCol = sql
62
+ ?.split(/[\(\),]/)
63
+ ?.find((it) => it.toLowerCase().includes('autoincrement'))
64
+ ?.trimStart()
65
+ ?.split(/\s+/)?.[0]
66
+ ?.replace(/["`]/g, '');
67
+ const columns = columnsByTable[name] ?? [];
68
+ // Otherwise, check for an INTEGER PRIMARY KEY
69
+ // https://www.sqlite.org/autoinc.html
70
+ if (!autoIncrementCol) {
71
+ const pkCols = columns.filter((r) => r.pk > 0);
72
+ if (pkCols.length === 1 && pkCols[0].type.toLowerCase() === 'integer') {
73
+ autoIncrementCol = pkCols[0].name;
74
+ }
75
+ }
76
+ return {
77
+ name: name,
78
+ isView: type === 'view',
79
+ columns: columns.map((col) => ({
80
+ name: col.name,
81
+ dataType: col.type,
82
+ isNullable: !col.notnull,
83
+ isAutoIncrementing: col.name === autoIncrementCol,
84
+ hasDefaultValue: col.dflt_value != null,
85
+ comment: undefined,
86
+ })),
87
+ };
88
+ });
67
89
  }
68
90
  }
@@ -444,6 +444,8 @@ export interface ExpressionBuilder<DB, TB extends keyof DB> {
444
444
  * inner join "pet" on "pet"."owner_id" = "person"."id"
445
445
  * group by "person"."id"
446
446
  * ```
447
+ *
448
+ * If you need a column reference, use {@link ref}.
447
449
  */
448
450
  table<T extends TB & string>(table: T): ExpressionWrapper<DB, TB, Selectable<DB[T]>>;
449
451
  /**
@@ -271,7 +271,7 @@ export interface MigratorProps {
271
271
  *
272
272
  * When false, migrations must be run in their exact alpha-numeric order.
273
273
  * This is checked against the migrations already run in the database
274
- * (`migrationTableName'). This ensures your migrations are always run in
274
+ * (`migrationTableName`). This ensures your migrations are always run in
275
275
  * the same order and is the safest option.
276
276
  *
277
277
  * When true, migrations are still run in alpha-numeric order, but
@@ -280,6 +280,13 @@ export interface MigratorProps {
280
280
  * order.
281
281
  */
282
282
  readonly allowUnorderedMigrations?: boolean;
283
+ /**
284
+ * A function that compares migration names, used when sorting migrations in
285
+ * ascending order.
286
+ *
287
+ * Default is `name0.localeCompare(name1)`.
288
+ */
289
+ readonly nameComparator?: (name0: string, name1: string) => number;
283
290
  }
284
291
  /**
285
292
  * All migration methods ({@link Migrator.migrateTo | migrateTo},
@@ -61,6 +61,7 @@ export class Migrator {
61
61
  .withPlugin(this.#schemaPlugin)
62
62
  .selectFrom(this.#migrationTable)
63
63
  .select(['name', 'timestamp'])
64
+ .$narrowType()
64
65
  .execute()
65
66
  : [];
66
67
  const migrations = await this.#resolveMigrations();
@@ -463,10 +464,19 @@ export class Migrator {
463
464
  const executedMigrations = await db
464
465
  .withPlugin(this.#schemaPlugin)
465
466
  .selectFrom(this.#migrationTable)
466
- .select('name')
467
- .orderBy(['timestamp', 'name'])
467
+ .select(['name', 'timestamp'])
468
+ .$narrowType()
468
469
  .execute();
469
- return executedMigrations.map((it) => it.name);
470
+ const nameComparator = this.#props.nameComparator || ((a, b) => a.localeCompare(b));
471
+ return (executedMigrations
472
+ // https://github.com/kysely-org/kysely/issues/843
473
+ .sort((a, b) => {
474
+ if (a.timestamp === b.timestamp) {
475
+ return nameComparator(a.name, b.name);
476
+ }
477
+ return (new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
478
+ })
479
+ .map((it) => it.name));
470
480
  }
471
481
  #ensureNoMissingMigrations(migrations, executedMigrations) {
472
482
  // Ensure all executed migrations exist in the `migrations` list.
@@ -40,9 +40,11 @@ function parseRowValues(row, columns) {
40
40
  length: columns.size,
41
41
  });
42
42
  let hasUndefinedOrComplexColumns = false;
43
+ let indexedRowColumns = rowColumns.length;
43
44
  for (const col of rowColumns) {
44
45
  const columnIdx = columns.get(col);
45
46
  if (isUndefined(columnIdx)) {
47
+ indexedRowColumns--;
46
48
  continue;
47
49
  }
48
50
  const value = row[col];
@@ -51,7 +53,7 @@ function parseRowValues(row, columns) {
51
53
  }
52
54
  rowValues[columnIdx] = value;
53
55
  }
54
- const hasMissingColumns = rowColumns.length < columns.size;
56
+ const hasMissingColumns = indexedRowColumns < columns.size;
55
57
  if (hasMissingColumns || hasUndefinedOrComplexColumns) {
56
58
  const defaultValue = DefaultInsertValueNode.create();
57
59
  return ValueListNode.create(rowValues.map((it) => isUndefined(it) ? defaultValue : parseValueExpression(it)));
@@ -20,7 +20,10 @@ export function createSnakeCaseMapper({ upperCase = false, underscoreBeforeDigit
20
20
  // If underScoreBeforeDigits is true then, well, insert an underscore
21
21
  // before digits :). Only the first digit gets an underscore if
22
22
  // there are multiple.
23
- if (underscoreBeforeDigits && isDigit(char) && !isDigit(prevChar)) {
23
+ if (underscoreBeforeDigits &&
24
+ isDigit(char) &&
25
+ !isDigit(prevChar) &&
26
+ !out.endsWith('_')) {
24
27
  out += '_' + char;
25
28
  continue;
26
29
  }
@@ -1,3 +1,5 @@
1
+ import { AggregateFunctionNode } from '../../operation-node/aggregate-function-node.js';
2
+ import { FunctionNode } from '../../operation-node/function-node.js';
1
3
  import { OperationNodeTransformer } from '../../operation-node/operation-node-transformer.js';
2
4
  import { OperationNode } from '../../operation-node/operation-node.js';
3
5
  import { ReferencesNode } from '../../operation-node/references-node.js';
@@ -8,4 +10,6 @@ export declare class WithSchemaTransformer extends OperationNodeTransformer {
8
10
  protected transformNodeImpl<T extends OperationNode>(node: T): T;
9
11
  protected transformSchemableIdentifier(node: SchemableIdentifierNode): SchemableIdentifierNode;
10
12
  protected transformReferences(node: ReferencesNode): ReferencesNode;
13
+ protected transformAggregateFunction(node: AggregateFunctionNode): AggregateFunctionNode;
14
+ protected transformFunction(node: FunctionNode): FunctionNode;
11
15
  }
@@ -29,6 +29,10 @@ const ROOT_OPERATION_NODES = freeze({
29
29
  UpdateQueryNode: true,
30
30
  MergeQueryNode: true,
31
31
  });
32
+ const SCHEMALESS_FUNCTIONS = {
33
+ json_agg: true,
34
+ to_json: true,
35
+ };
32
36
  export class WithSchemaTransformer extends OperationNodeTransformer {
33
37
  #schema;
34
38
  #schemableIds = new Set();
@@ -78,6 +82,28 @@ export class WithSchemaTransformer extends OperationNodeTransformer {
78
82
  table: TableNode.createWithSchema(this.#schema, transformed.table.table.identifier.name),
79
83
  };
80
84
  }
85
+ transformAggregateFunction(node) {
86
+ return {
87
+ ...super.transformAggregateFunction({ ...node, aggregated: [] }),
88
+ aggregated: this.#transformTableArgsWithoutSchemas(node, 'aggregated'),
89
+ };
90
+ }
91
+ transformFunction(node) {
92
+ return {
93
+ ...super.transformFunction({ ...node, arguments: [] }),
94
+ arguments: this.#transformTableArgsWithoutSchemas(node, 'arguments'),
95
+ };
96
+ }
97
+ #transformTableArgsWithoutSchemas(node, argsKey) {
98
+ return SCHEMALESS_FUNCTIONS[node.func]
99
+ ? node[argsKey].map((arg) => !TableNode.is(arg) || arg.table.schema
100
+ ? this.transformNode(arg)
101
+ : {
102
+ ...arg,
103
+ table: this.transformIdentifier(arg.table.identifier),
104
+ })
105
+ : this.transformNodeList(node[argsKey]);
106
+ }
81
107
  #isRootOperationNode(node) {
82
108
  return node.kind in ROOT_OPERATION_NODES;
83
109
  }
@@ -199,7 +199,7 @@ export interface Sql {
199
199
  * select first_name from "person"
200
200
  * ```
201
201
  *
202
- * The refefences can also include a schema on supported databases:
202
+ * The references can also include a schema on supported databases:
203
203
  *
204
204
  * ```ts
205
205
  * const table = 'public.person'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kysely",
3
- "version": "0.27.5",
3
+ "version": "0.27.6",
4
4
  "description": "Type safe SQL query builder",
5
5
  "repository": {
6
6
  "type": "git",