d1-sql-tag 0.2.0 → 0.2.2

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.
package/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  # `d1-sql-tag`
2
2
 
3
+ [![npm version](https://badge.fury.io/js/d1-sql-tag.svg)](https://badge.fury.io/js/d1-sql-tag)
4
+
3
5
  A template literal for working with [Cloudflare D1](https://developers.cloudflare.com/d1/)
4
6
  database.
5
7
 
@@ -10,6 +12,14 @@ database.
10
12
  If you have created a D1 database and configured it with the binding name `DB`,
11
13
  in `wrangler.toml`, you can create a template literal tag with `createD1SqlTag()`.
12
14
 
15
+ We also set up a callback to log stats for each query, like so:
16
+
17
+ ```
18
+ D1 batch: 286ms · 1 queries
19
+ 1: SELECT ?1 AS message
20
+ ↳ 0.3053ms · 0 changed · 0 read · 0 written
21
+ ```
22
+
13
23
  ```ts
14
24
  import { createD1SqlTag, logQueryResults } from "d1-sql-tag";
15
25
 
@@ -26,11 +36,7 @@ function createSqlTag(db: D1Database) {
26
36
  }
27
37
 
28
38
  export default {
29
- async fetch(
30
- request: Request,
31
- env: Env,
32
- ctx: ExecutionContext,
33
- ): Promise<Response> {
39
+ async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
34
40
  const sql = createSqlTag(env.DB);
35
41
  const result = await sql`SELECT ${"hello world"} AS message`.all<{
36
42
  message: string;
@@ -40,6 +46,63 @@ export default {
40
46
  };
41
47
  ```
42
48
 
49
+ ## Usage with Hono on Cloudflare Workers
50
+
51
+ If you have created a D1 database and configured it with the binding name `DB`,
52
+ in `wrangler.toml`, you can create a template literal tag with `createD1SqlTag()`.
53
+
54
+ We also set up a callback to log stats for each query, like so:
55
+
56
+ ```
57
+ D1 batch: 286ms · 1 queries
58
+ 1: SELECT ?1 AS message
59
+ ↳ 0.3053ms · 0 changed · 0 read · 0 written
60
+ ```
61
+
62
+ Additionally, we use [`hono/timing`](https://hono.dev/middleware/builtin/timing)
63
+ to send [`Server-Timing`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Server-Timing) headers for the total response time, how long we wait for each batch, and how
64
+ long each query takes. Open the network tab in your browser's devtools, select
65
+ the request and look at the "Timing" tab.
66
+
67
+ ```ts
68
+ import { createD1SqlTag, logQueryResults } from "d1-sql-tag";
69
+ import { Hono, type Context } from "hono";
70
+ import { endTime, setMetric, startTime, timing } from "hono/timing";
71
+
72
+ type Bindings = {
73
+ DB: D1Database;
74
+ };
75
+
76
+ const app = new Hono<{ Bindings: Bindings }>();
77
+
78
+ function createSqlTag(c: Context<{ Bindings: Bindings }>) {
79
+ return createD1SqlTag(c.env.DB, {
80
+ beforeQuery(batchId, queries) {
81
+ startTime(c, `db-${batchId}`);
82
+ },
83
+ afterQuery(batchId, queries, results, duration) {
84
+ endTime(c, `db-${batchId}`);
85
+ results.forEach((result, i) => {
86
+ setMetric(c, `db-${batchId}-query-${i + 1}`, result.meta.duration);
87
+ });
88
+ logQueryResults(queries, results, duration);
89
+ },
90
+ });
91
+ }
92
+
93
+ app.use("*", timing());
94
+
95
+ app.get("/", async (c) => {
96
+ const sql = createSqlTag(c);
97
+ const result = await sql`SELECT ${"hello world"} AS message`.all<{
98
+ message: string;
99
+ }>();
100
+ return c.text(`Message: ${result.results[0].message}`);
101
+ });
102
+
103
+ export default app;
104
+ ```
105
+
43
106
  ## License
44
107
 
45
108
  [MIT](./LICENSE.txt)
package/dist/index.js CHANGED
@@ -23,9 +23,7 @@ function createD1SqlTag(db, options) {
23
23
  const id = makeBatchId();
24
24
  options?.beforeQuery?.(id, queries);
25
25
  const start = Date.now();
26
- const result = await db.batch(
27
- statements.map((it) => makeNativeStatement(db, it))
28
- );
26
+ const result = await db.batch(statements.map((it) => makeNativeStatement(db, it)));
29
27
  const duration = Date.now() - start;
30
28
  options?.afterQuery?.(id, queries, result, duration);
31
29
  for (let i = 0; i < result.length; i++) {
@@ -80,7 +78,10 @@ function expandTemplate(rootTemplateStrings, rootTemplateValues) {
80
78
  if (valueIsFragment) {
81
79
  expand(value.templateStrings, value.templateValues);
82
80
  } else {
83
- const valueIndex = values.push(value) - 1;
81
+ let valueIndex = values.indexOf(value);
82
+ if (valueIndex === -1) {
83
+ valueIndex = values.push(value) - 1;
84
+ }
84
85
  query += `?${valueIndex + 1}`;
85
86
  }
86
87
  }
@@ -94,17 +95,9 @@ async function executeAll(db, options, statement, mapper) {
94
95
  const batchId2 = makeBatchId();
95
96
  options?.beforeQuery?.(batchId2, [statement.query]);
96
97
  const start = Date.now();
97
- const result = await makeNativeStatement(
98
- db,
99
- statement
100
- ).all();
98
+ const result = await makeNativeStatement(db, statement).all();
101
99
  const duration = Date.now() - start;
102
- options?.afterQuery?.(
103
- batchId2,
104
- [statement.query],
105
- [result],
106
- duration
107
- );
100
+ options?.afterQuery?.(batchId2, [statement.query], [result], duration);
108
101
  if (mapper) {
109
102
  result.results = result.results.map(mapper);
110
103
  }
@@ -116,12 +109,7 @@ async function executeRun(db, options, statement) {
116
109
  const start = Date.now();
117
110
  const result = await makeNativeStatement(db, statement).run();
118
111
  const duration = Date.now() - start;
119
- options?.afterQuery?.(
120
- batchId2,
121
- [statement.query],
122
- [result],
123
- duration
124
- );
112
+ options?.afterQuery?.(batchId2, [statement.query], [result], duration);
125
113
  return result;
126
114
  }
127
115
  function makeNativeStatement(db, statement) {
@@ -146,9 +134,7 @@ function logQueryResults(queries, results, duration) {
146
134
  const result = results[i];
147
135
  console.log(`${i + 1}: ${cleanupSqlQuery(query)}`);
148
136
  const logSuffix = "rows_read" in result.meta ? ` \xB7 ${result.meta.rows_read} read \xB7 ${result.meta.rows_written} written` : "";
149
- console.log(
150
- ` \u21B3 ${result.meta.duration}ms \xB7 ${result.meta.changes} changed` + logSuffix
151
- );
137
+ console.log(` \u21B3 ${result.meta.duration}ms \xB7 ${result.meta.changes} changed` + logSuffix);
152
138
  }
153
139
  }
154
140
  function cleanupSqlQuery(query) {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sql-tag.ts","../src/logger.ts"],"sourcesContent":["import type { D1Database, D1Result } from \"@cloudflare/workers-types\";\n\nexport type Primitive = string | number | boolean | null;\n\nexport type SqlTag = ((\n strings: TemplateStringsArray,\n ...values: (Primitive | SqlQueryFragment)[]\n) => SqlQueryFragment) & {\n batch<T extends readonly PreparedStatementBase<object>[]>(\n statements: T,\n ): Promise<{\n -readonly [P in keyof T]: SqlResult<RowType<T[P]>>;\n }>;\n};\n\nexport interface SqlQueryFragment {\n build<\n T extends object = Record<string, Primitive>,\n >(): RawPreparedStatement<T>;\n all<T extends object = Record<string, Primitive>>(): Promise<D1Result<T>>;\n run(): Promise<D1Result>;\n templateStrings: TemplateStringsArray;\n templateValues: (Primitive | SqlQueryFragment)[];\n}\n\ninterface PreparedStatementBase<T extends object> {\n query: string;\n values: Primitive[];\n all(): Promise<D1Result<T>>;\n run(): Promise<D1Result>;\n [rowTypeSymbol]: T;\n}\n\ninterface MappedPreparedStatement<TRaw extends object, TMapped extends object>\n extends PreparedStatementBase<TMapped> {\n mapper: (row: TRaw) => TMapped;\n}\n\ninterface RawPreparedStatement<T extends object>\n extends PreparedStatementBase<T> {\n map<TMapped extends object>(\n mapper: (row: T) => TMapped,\n ): MappedPreparedStatement<T, TMapped>;\n}\n\ntype PreparedStatement<\n T extends object,\n U extends object = Record<string, Primitive>,\n> = RawPreparedStatement<T> | MappedPreparedStatement<U, T>;\n\nexport type RowType<\n T extends\n | PreparedStatementBase<any>\n | ((...args: any) => PreparedStatementBase<any>),\n> = T extends PreparedStatementBase<any>\n ? T[typeof rowTypeSymbol]\n : T extends (...args: any) => PreparedStatementBase<any>\n ? ReturnType<T>[typeof rowTypeSymbol]\n : never;\n\nexport interface SqlResult<T extends object = Record<string, Primitive>>\n extends D1Result<T> {}\n\ninterface SqlTagOptions {\n beforeQuery?: (id: number, queries: string[]) => void;\n afterQuery?: (\n id: number,\n queries: string[],\n results: SqlResult[],\n duration: number,\n ) => void;\n}\n\nlet batchId = 0;\nconst rowTypeSymbol = Symbol(\"rowType\");\n\nexport function createD1SqlTag(\n db: D1Database,\n options?: SqlTagOptions,\n): SqlTag {\n const sqlTag: SqlTag = (strings, ...values): SqlQueryFragment => {\n const fragment: SqlQueryFragment = {\n build() {\n return buildPreparedStatement(db, options, strings, values);\n },\n all<T extends object>() {\n return buildPreparedStatement<T>(db, options, strings, values).all();\n },\n run() {\n return buildPreparedStatement(db, options, strings, values).run();\n },\n templateStrings: strings,\n templateValues: values,\n };\n return fragment;\n };\n sqlTag.batch = async (statements) => {\n const queries = statements.map((it) => it.query);\n\n const id = makeBatchId();\n options?.beforeQuery?.(id, queries);\n const start = Date.now();\n const result = (await db.batch(\n statements.map((it) => makeNativeStatement(db, it)),\n )) as any;\n const duration = Date.now() - start;\n options?.afterQuery?.(id, queries, result, duration);\n\n for (let i = 0; i < result.length; i++) {\n const statement = statements[i];\n const statementResult = result[i];\n if (\"mapper\" in statement) {\n statementResult.results = statementResult.results.map(statement.mapper);\n }\n }\n\n return result;\n };\n return sqlTag;\n}\n\nfunction buildPreparedStatement<T extends object>(\n db: D1Database,\n options: SqlTagOptions | undefined,\n templateStrings: TemplateStringsArray,\n templateValues: (Primitive | SqlQueryFragment)[],\n): RawPreparedStatement<T> {\n const { query, values } = expandTemplate(templateStrings, templateValues);\n\n const statement: RawPreparedStatement<T> = {\n all() {\n return executeAll(db, options, statement, null);\n },\n run() {\n return executeRun(db, options, statement);\n },\n map<U extends object>(mapper: (row: T) => U) {\n const mappedStatement: MappedPreparedStatement<T, U> = {\n all() {\n return executeAll(db, options, mappedStatement, mapper);\n },\n run() {\n return executeRun(db, options, mappedStatement);\n },\n query,\n values,\n mapper,\n [rowTypeSymbol]: null as any,\n };\n return mappedStatement;\n },\n query,\n values,\n [rowTypeSymbol]: null as any,\n };\n\n return statement;\n}\n\nfunction expandTemplate(\n rootTemplateStrings: TemplateStringsArray,\n rootTemplateValues: (Primitive | SqlQueryFragment)[],\n) {\n let query = \"\";\n const values: Primitive[] = [];\n\n function expand(\n templateStrings: TemplateStringsArray,\n templateValues: (Primitive | SqlQueryFragment)[],\n ) {\n for (let i = 0; i < templateStrings.length; i++) {\n if (i > 0) {\n const value = templateValues[i - 1];\n const valueIsFragment =\n value &&\n typeof value === \"object\" &&\n \"templateStrings\" in value &&\n \"templateValues\" in value;\n if (valueIsFragment) {\n expand(value.templateStrings, value.templateValues);\n } else {\n const valueIndex = values.push(value as Primitive) - 1;\n query += `?${valueIndex + 1}`;\n }\n }\n query += templateStrings[i];\n }\n }\n\n expand(rootTemplateStrings, rootTemplateValues);\n\n return { query, values };\n}\n\nasync function executeAll<TRaw extends object, TMapped extends object>(\n db: D1Database,\n options: SqlTagOptions | undefined,\n statement:\n | RawPreparedStatement<TRaw>\n | MappedPreparedStatement<TRaw, TMapped>,\n mapper: ((row: TRaw) => TMapped) | null,\n) {\n const batchId = makeBatchId();\n options?.beforeQuery?.(batchId, [statement.query]);\n const start = Date.now();\n\n const result = (await makeNativeStatement(\n db,\n statement as any,\n ).all()) as SqlResult<TMapped>;\n\n const duration = Date.now() - start;\n options?.afterQuery?.(\n batchId,\n [statement.query],\n [result as SqlResult],\n duration,\n );\n\n if (mapper) {\n result.results = result.results.map(mapper as any);\n }\n\n return result;\n}\n\nasync function executeRun<T extends object, U extends object>(\n db: D1Database,\n options: SqlTagOptions | undefined,\n statement: PreparedStatement<T, U>,\n) {\n const batchId = makeBatchId();\n options?.beforeQuery?.(batchId, [statement.query]);\n const start = Date.now();\n const result = await makeNativeStatement(db, statement).run();\n const duration = Date.now() - start;\n options?.afterQuery?.(\n batchId,\n [statement.query],\n [result as SqlResult],\n duration,\n );\n return result;\n}\n\nfunction makeNativeStatement<T extends object>(\n db: D1Database,\n statement: PreparedStatementBase<T>,\n) {\n let stmt = db.prepare(statement.query);\n if (statement.values.length > 0) {\n stmt = stmt.bind(...statement.values);\n }\n return stmt;\n}\n\nfunction makeBatchId() {\n batchId += 1;\n return batchId;\n}\n","import type { SqlResult } from \"./sql-tag.js\";\n\nexport function logQueryResults(\n queries: string[],\n results: SqlResult[],\n duration?: number,\n) {\n console.log(\n `D1 batch: ${typeof duration === \"number\" ? `${duration}ms · ` : \"\"}${\n queries.length\n } queries`,\n );\n for (let i = 0; i < queries.length; i++) {\n const query = queries[i];\n const result = results[i];\n\n console.log(`${i + 1}: ${cleanupSqlQuery(query)}`);\n\n const logSuffix =\n \"rows_read\" in result.meta\n ? ` · ${result.meta.rows_read} read · ${result.meta.rows_written} written`\n : \"\";\n console.log(\n ` ↳ ${result.meta.duration}ms · ${result.meta.changes} changed` +\n logSuffix,\n );\n }\n}\n\nfunction cleanupSqlQuery(query: string) {\n return query.replace(/\\n/g, \" \").replace(/\\s+/g, \" \");\n}\n"],"mappings":"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","names":["batchId"]}
1
+ {"version":3,"sources":["../src/sql-tag.ts","../src/logger.ts"],"sourcesContent":["import type { D1Database, D1Result } from \"@cloudflare/workers-types\";\n\nexport type Primitive = string | number | boolean | null;\n\nexport type SqlTag = ((\n strings: TemplateStringsArray,\n ...values: (Primitive | SqlQueryFragment)[]\n) => SqlQueryFragment) & {\n batch<T extends readonly PreparedStatementBase<object>[]>(\n statements: T,\n ): Promise<{\n -readonly [P in keyof T]: SqlResult<RowType<T[P]>>;\n }>;\n};\n\nexport interface SqlQueryFragment {\n build<T extends object = Record<string, Primitive>>(): RawPreparedStatement<T>;\n all<T extends object = Record<string, Primitive>>(): Promise<D1Result<T>>;\n run(): Promise<D1Result>;\n templateStrings: TemplateStringsArray;\n templateValues: (Primitive | SqlQueryFragment)[];\n}\n\ninterface PreparedStatementBase<T extends object> {\n query: string;\n values: Primitive[];\n all(): Promise<D1Result<T>>;\n run(): Promise<D1Result>;\n [rowTypeSymbol]: T;\n}\n\ninterface MappedPreparedStatement<TRaw extends object, TMapped extends object>\n extends PreparedStatementBase<TMapped> {\n mapper: (row: TRaw) => TMapped;\n}\n\ninterface RawPreparedStatement<T extends object> extends PreparedStatementBase<T> {\n map<TMapped extends object>(mapper: (row: T) => TMapped): MappedPreparedStatement<T, TMapped>;\n}\n\ntype PreparedStatement<T extends object, U extends object = Record<string, Primitive>> =\n | RawPreparedStatement<T>\n | MappedPreparedStatement<U, T>;\n\nexport type RowType<\n T extends PreparedStatementBase<any> | ((...args: any) => PreparedStatementBase<any>),\n> = T extends PreparedStatementBase<any>\n ? T[typeof rowTypeSymbol]\n : T extends (...args: any) => PreparedStatementBase<any>\n ? ReturnType<T>[typeof rowTypeSymbol]\n : never;\n\nexport interface SqlResult<T extends object = Record<string, Primitive>> extends D1Result<T> {}\n\ninterface SqlTagOptions {\n beforeQuery?: (id: number, queries: string[]) => void;\n afterQuery?: (id: number, queries: string[], results: SqlResult[], duration: number) => void;\n}\n\nlet batchId = 0;\nconst rowTypeSymbol = Symbol(\"rowType\");\n\nexport function createD1SqlTag(db: D1Database, options?: SqlTagOptions): SqlTag {\n const sqlTag: SqlTag = (strings, ...values): SqlQueryFragment => {\n const fragment: SqlQueryFragment = {\n build() {\n return buildPreparedStatement(db, options, strings, values);\n },\n all<T extends object>() {\n return buildPreparedStatement<T>(db, options, strings, values).all();\n },\n run() {\n return buildPreparedStatement(db, options, strings, values).run();\n },\n templateStrings: strings,\n templateValues: values,\n };\n return fragment;\n };\n sqlTag.batch = async (statements) => {\n const queries = statements.map((it) => it.query);\n\n const id = makeBatchId();\n options?.beforeQuery?.(id, queries);\n const start = Date.now();\n const result = (await db.batch(statements.map((it) => makeNativeStatement(db, it)))) as any;\n const duration = Date.now() - start;\n options?.afterQuery?.(id, queries, result, duration);\n\n for (let i = 0; i < result.length; i++) {\n const statement = statements[i];\n const statementResult = result[i];\n if (\"mapper\" in statement) {\n statementResult.results = statementResult.results.map(statement.mapper);\n }\n }\n\n return result;\n };\n return sqlTag;\n}\n\nfunction buildPreparedStatement<T extends object>(\n db: D1Database,\n options: SqlTagOptions | undefined,\n templateStrings: TemplateStringsArray,\n templateValues: (Primitive | SqlQueryFragment)[],\n): RawPreparedStatement<T> {\n const { query, values } = expandTemplate(templateStrings, templateValues);\n\n const statement: RawPreparedStatement<T> = {\n all() {\n return executeAll(db, options, statement, null);\n },\n run() {\n return executeRun(db, options, statement);\n },\n map<U extends object>(mapper: (row: T) => U) {\n const mappedStatement: MappedPreparedStatement<T, U> = {\n all() {\n return executeAll(db, options, mappedStatement, mapper);\n },\n run() {\n return executeRun(db, options, mappedStatement);\n },\n query,\n values,\n mapper,\n [rowTypeSymbol]: null as any,\n };\n return mappedStatement;\n },\n query,\n values,\n [rowTypeSymbol]: null as any,\n };\n\n return statement;\n}\n\nfunction expandTemplate(\n rootTemplateStrings: TemplateStringsArray,\n rootTemplateValues: (Primitive | SqlQueryFragment)[],\n) {\n let query = \"\";\n const values: Primitive[] = [];\n\n function expand(\n templateStrings: TemplateStringsArray,\n templateValues: (Primitive | SqlQueryFragment)[],\n ) {\n for (let i = 0; i < templateStrings.length; i++) {\n if (i > 0) {\n const value = templateValues[i - 1];\n const valueIsFragment =\n value &&\n typeof value === \"object\" &&\n \"templateStrings\" in value &&\n \"templateValues\" in value;\n\n if (valueIsFragment) {\n expand(value.templateStrings, value.templateValues);\n } else {\n let valueIndex = values.indexOf(value);\n if (valueIndex === -1) {\n valueIndex = values.push(value) - 1;\n }\n query += `?${valueIndex + 1}`;\n }\n }\n query += templateStrings[i];\n }\n }\n\n expand(rootTemplateStrings, rootTemplateValues);\n\n return { query, values };\n}\n\nasync function executeAll<TRaw extends object, TMapped extends object>(\n db: D1Database,\n options: SqlTagOptions | undefined,\n statement: RawPreparedStatement<TRaw> | MappedPreparedStatement<TRaw, TMapped>,\n mapper: ((row: TRaw) => TMapped) | null,\n) {\n const batchId = makeBatchId();\n options?.beforeQuery?.(batchId, [statement.query]);\n const start = Date.now();\n\n const result = (await makeNativeStatement(db, statement as any).all()) as SqlResult<TMapped>;\n\n const duration = Date.now() - start;\n options?.afterQuery?.(batchId, [statement.query], [result as SqlResult], duration);\n\n if (mapper) {\n result.results = result.results.map(mapper as any);\n }\n\n return result;\n}\n\nasync function executeRun<T extends object, U extends object>(\n db: D1Database,\n options: SqlTagOptions | undefined,\n statement: PreparedStatement<T, U>,\n) {\n const batchId = makeBatchId();\n options?.beforeQuery?.(batchId, [statement.query]);\n const start = Date.now();\n const result = await makeNativeStatement(db, statement).run();\n const duration = Date.now() - start;\n options?.afterQuery?.(batchId, [statement.query], [result as SqlResult], duration);\n return result;\n}\n\nfunction makeNativeStatement<T extends object>(\n db: D1Database,\n statement: PreparedStatementBase<T>,\n) {\n let stmt = db.prepare(statement.query);\n if (statement.values.length > 0) {\n stmt = stmt.bind(...statement.values);\n }\n return stmt;\n}\n\nfunction makeBatchId() {\n batchId += 1;\n return batchId;\n}\n","import type { SqlResult } from \"./sql-tag.js\";\n\nexport function logQueryResults(queries: string[], results: SqlResult[], duration?: number) {\n console.log(\n `D1 batch: ${typeof duration === \"number\" ? `${duration}ms · ` : \"\"}${queries.length} queries`,\n );\n for (let i = 0; i < queries.length; i++) {\n const query = queries[i];\n const result = results[i];\n\n console.log(`${i + 1}: ${cleanupSqlQuery(query)}`);\n\n const logSuffix =\n \"rows_read\" in result.meta\n ? ` · ${result.meta.rows_read} read · ${result.meta.rows_written} written`\n : \"\";\n console.log(` ↳ ${result.meta.duration}ms · ${result.meta.changes} changed` + logSuffix);\n }\n}\n\nfunction cleanupSqlQuery(query: string) {\n return query.replace(/\\n/g, \" \").replace(/\\s+/g, \" \");\n}\n"],"mappings":"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","names":["batchId"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "d1-sql-tag",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "A template literal for working with Cloudflare D1 database",
5
5
  "keywords": [
6
6
  "d1",
@@ -17,12 +17,17 @@
17
17
  "type": "git",
18
18
  "url": "git+https://github.com/jonasb/d1-sql-tag.git"
19
19
  },
20
+ "publishConfig": {
21
+ "provenance": true
22
+ },
20
23
  "scripts": {
21
24
  "build": "tsup src/index.ts --format esm --sourcemap --dts",
25
+ "changeset": "changeset",
22
26
  "prettier:check": "prettier --check .",
23
27
  "prettier:fix": "prettier --write .",
24
28
  "release": "npm run build && npm run test && changeset publish",
25
- "test": "node --import tsx --test test/sql-tag.test.ts"
29
+ "test": "node --import tsx --test test/sql-tag.test.ts",
30
+ "test:watch": "node --import tsx --watch --test test/sql-tag.test.ts"
26
31
  },
27
32
  "devDependencies": {
28
33
  "@changesets/cli": "^2.27.1",