@triptease/sql-template 0.24.25 → 0.36.7

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/Expression.d.ts CHANGED
@@ -1,2 +1,24 @@
1
- export declare class Expression {
1
+ /**
2
+ * Brand used to identify expressions at runtime.
3
+ *
4
+ * It is registered with `Symbol.for` so that expressions created by a different copy of this package
5
+ * (e.g. a duplicated install, or a different version pulled in by an adapter) are still recognised,
6
+ * which `instanceof` cannot do. It is a symbol rather than a string field so that untrusted data
7
+ * (e.g. the output of `JSON.parse`) can never masquerade as an expression.
8
+ */
9
+ export declare const kind: unique symbol;
10
+ /** The kinds of expression understood by adapters. Adapters must throw on anything else. */
11
+ export type Kind = 'text' | 'identifier' | 'value' | 'template';
12
+ export interface Expression {
13
+ readonly [kind]: Kind;
2
14
  }
15
+ /**
16
+ * Base class of all expressions. Every concrete expression defines its `kind` brand on its prototype.
17
+ * Subclassing it outside this package is not supported: such instances have no brand and are rejected
18
+ * by `SQL`, `value` and `template` (compose the built-in expressions instead).
19
+ */
20
+ export declare abstract class Expression {
21
+ }
22
+ /** The kind of `value` if it is an expression (from any copy of this package), otherwise `undefined`. */
23
+ export declare function kindOf(value: unknown): string | undefined;
24
+ export declare function isExpression(value: unknown): value is Expression;
package/Expression.js CHANGED
@@ -1,7 +1,27 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.Expression = void 0;
4
- class Expression {
1
+ /**
2
+ * Brand used to identify expressions at runtime.
3
+ *
4
+ * It is registered with `Symbol.for` so that expressions created by a different copy of this package
5
+ * (e.g. a duplicated install, or a different version pulled in by an adapter) are still recognised,
6
+ * which `instanceof` cannot do. It is a symbol rather than a string field so that untrusted data
7
+ * (e.g. the output of `JSON.parse`) can never masquerade as an expression.
8
+ */
9
+ export const kind = Symbol.for('@triptease/sql-template/kind');
10
+ /**
11
+ * Base class of all expressions. Every concrete expression defines its `kind` brand on its prototype.
12
+ * Subclassing it outside this package is not supported: such instances have no brand and are rejected
13
+ * by `SQL`, `value` and `template` (compose the built-in expressions instead).
14
+ */
15
+ export class Expression {
16
+ }
17
+ /** The kind of `value` if it is an expression (from any copy of this package), otherwise `undefined`. */
18
+ export function kindOf(value) {
19
+ if (typeof value !== 'object' || value === null)
20
+ return undefined;
21
+ const k = value[kind];
22
+ return typeof k === 'string' ? k : undefined;
23
+ }
24
+ export function isExpression(value) {
25
+ return kindOf(value) !== undefined;
5
26
  }
6
- exports.Expression = Expression;
7
27
  //# sourceMappingURL=Expression.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Expression.js","sourceRoot":"","sources":["../src/Expression.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,IAAI,GAAkB,MAAM,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;AAS9E;;;;GAIG;AACH,MAAM,OAAgB,UAAU;CAC/B;AAED,yGAAyG;AACzG,MAAM,UAAU,MAAM,CAAC,KAAc;IACjC,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,SAAS,CAAC;IAClE,MAAM,CAAC,GAAa,KAA8B,CAAC,IAAI,CAAC,CAAC;IACzD,OAAO,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAc;IACvC,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,SAAS,CAAC;AACvC,CAAC","sourcesContent":["/**\n * Brand used to identify expressions at runtime.\n *\n * It is registered with `Symbol.for` so that expressions created by a different copy of this package\n * (e.g. a duplicated install, or a different version pulled in by an adapter) are still recognised,\n * which `instanceof` cannot do. It is a symbol rather than a string field so that untrusted data\n * (e.g. the output of `JSON.parse`) can never masquerade as an expression.\n */\nexport const kind: unique symbol = Symbol.for('@triptease/sql-template/kind');\n\n/** The kinds of expression understood by adapters. Adapters must throw on anything else. */\nexport type Kind = 'text' | 'identifier' | 'value' | 'template';\n\nexport interface Expression {\n readonly [kind]: Kind;\n}\n\n/**\n * Base class of all expressions. Every concrete expression defines its `kind` brand on its prototype.\n * Subclassing it outside this package is not supported: such instances have no brand and are rejected\n * by `SQL`, `value` and `template` (compose the built-in expressions instead).\n */\nexport abstract class Expression {\n}\n\n/** The kind of `value` if it is an expression (from any copy of this package), otherwise `undefined`. */\nexport function kindOf(value: unknown): string | undefined {\n if (typeof value !== 'object' || value === null) return undefined;\n const k: unknown = (value as { [kind]?: unknown })[kind];\n return typeof k === 'string' ? k : undefined;\n}\n\nexport function isExpression(value: unknown): value is Expression {\n return kindOf(value) !== undefined;\n}\n"]}
package/Identifier.d.ts CHANGED
@@ -1,8 +1,17 @@
1
- import { Expression } from "./Expression";
2
- import { Template } from "./Template";
1
+ import { Expression, kind } from "./Expression.js";
2
+ import { Template } from "./Template.js";
3
+ export interface Identifier {
4
+ readonly [kind]: 'identifier';
5
+ }
6
+ /** A dynamic identifier (table, column, ...) that adapters escape. */
3
7
  export declare class Identifier extends Expression {
4
8
  readonly identifier: string;
5
9
  constructor(identifier: string);
6
10
  }
11
+ export declare function isIdentifier(value: unknown): value is Identifier;
7
12
  export declare function id(identifier: string): Identifier;
8
- export declare function ids(identifiers: any[], separator?: string): Template;
13
+ /**
14
+ * Multiple identifiers separated by `separator` (default `text(', ')`).
15
+ * The separator is an Expression so any raw SQL is explicit at the call site.
16
+ */
17
+ export declare function ids(identifiers: readonly string[], separator?: Expression): Template;
package/Identifier.js CHANGED
@@ -1,22 +1,31 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ids = exports.id = exports.Identifier = void 0;
4
- const Expression_1 = require("./Expression");
5
- const Template_1 = require("./Template");
6
- const Text_1 = require("./Text");
7
- class Identifier extends Expression_1.Expression {
1
+ import { Expression, kind, kindOf } from "./Expression.js";
2
+ import { template, Template } from "./Template.js";
3
+ import { text } from "./Text.js";
4
+ import { separated } from "./separated.js";
5
+ /** A dynamic identifier (table, column, ...) that adapters escape. */
6
+ export class Identifier extends Expression {
7
+ identifier;
8
8
  constructor(identifier) {
9
9
  super();
10
+ if (typeof identifier !== 'string')
11
+ throw new TypeError(`Identifier must be a string but was ${typeof identifier}`);
10
12
  this.identifier = identifier;
13
+ if (new.target === Identifier)
14
+ Object.freeze(this);
11
15
  }
12
16
  }
13
- exports.Identifier = Identifier;
14
- function id(identifier) {
17
+ Object.defineProperty(Identifier.prototype, kind, { value: 'identifier' });
18
+ export function isIdentifier(value) {
19
+ return kindOf(value) === 'identifier';
20
+ }
21
+ export function id(identifier) {
15
22
  return new Identifier(identifier);
16
23
  }
17
- exports.id = id;
18
- function ids(identifiers, separator = ', ') {
19
- return (0, Template_1.template)(...identifiers.flatMap((v, i) => i > 0 ? [(0, Text_1.text)(separator), id(v)] : [id(v)]));
24
+ /**
25
+ * Multiple identifiers separated by `separator` (default `text(', ')`).
26
+ * The separator is an Expression so any raw SQL is explicit at the call site.
27
+ */
28
+ export function ids(identifiers, separator = text(', ')) {
29
+ return template(...separated(identifiers.map(id), separator));
20
30
  }
21
- exports.ids = ids;
22
31
  //# sourceMappingURL=Identifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Identifier.js","sourceRoot":"","sources":["../src/Identifier.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAE,IAAI,EAAE,MAAM,EAAC,MAAM,iBAAiB,CAAC;AACzD,OAAO,EAAC,QAAQ,EAAE,QAAQ,EAAC,MAAM,eAAe,CAAC;AACjD,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AAC/B,OAAO,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAMzC,sEAAsE;AACtE,MAAM,OAAO,UAAW,SAAQ,UAAU;IAC7B,UAAU,CAAS;IAE5B,YAAY,UAAkB;QAC1B,KAAK,EAAE,CAAC;QACR,IAAI,OAAO,UAAU,KAAK,QAAQ;YAAE,MAAM,IAAI,SAAS,CAAC,uCAAuC,OAAO,UAAU,EAAE,CAAC,CAAC;QACpH,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,IAAI,MAAM,KAAK,UAAU;YAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACvD,CAAC;CACJ;AAED,MAAM,CAAC,cAAc,CAAC,UAAU,CAAC,SAAS,EAAE,IAAI,EAAE,EAAC,KAAK,EAAE,YAAY,EAAC,CAAC,CAAC;AAEzE,MAAM,UAAU,YAAY,CAAC,KAAc;IACvC,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,YAAY,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,EAAE,CAAC,UAAkB;IACjC,OAAO,IAAI,UAAU,CAAC,UAAU,CAAC,CAAC;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,GAAG,CAAC,WAA8B,EAAE,SAAS,GAAe,IAAI,CAAC,IAAI,CAAC;IAClF,OAAO,QAAQ,CAAC,GAAG,SAAS,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;AAClE,CAAC","sourcesContent":["import {Expression, kind, kindOf} from \"./Expression.js\";\nimport {template, Template} from \"./Template.js\";\nimport {text} from \"./Text.js\";\nimport {separated} from \"./separated.js\";\n\nexport interface Identifier {\n readonly [kind]: 'identifier';\n}\n\n/** A dynamic identifier (table, column, ...) that adapters escape. */\nexport class Identifier extends Expression {\n readonly identifier: string;\n\n constructor(identifier: string) {\n super();\n if (typeof identifier !== 'string') throw new TypeError(`Identifier must be a string but was ${typeof identifier}`);\n this.identifier = identifier;\n if (new.target === Identifier) Object.freeze(this);\n }\n}\n\nObject.defineProperty(Identifier.prototype, kind, {value: 'identifier'});\n\nexport function isIdentifier(value: unknown): value is Identifier {\n return kindOf(value) === 'identifier';\n}\n\nexport function id(identifier: string): Identifier {\n return new Identifier(identifier);\n}\n\n/**\n * Multiple identifiers separated by `separator` (default `text(', ')`).\n * The separator is an Expression so any raw SQL is explicit at the call site.\n */\nexport function ids(identifiers: readonly string[], separator: Expression = text(', ')): Template {\n return template(...separated(identifiers.map(id), separator));\n}\n"]}
package/README.md CHANGED
@@ -1,56 +1,203 @@
1
- # Sql Template
2
-
3
- This is yet another SQL tagged template for Typescript/Javascript
4
-
5
- ## Why another library?
6
-
7
- * Typescript first
8
- * Functional/Immutable
9
- * Super simple implementation (the main SQL function is [3 lines of code](https://github.com/triptease/sql-template/blob/master/sql-template/src/SQL.ts#L5))
10
- * Full escaping of identifiers and values
11
- * Plugable to any DB (currently only Postgres @triptease\sql-template-postgres )
12
- * Automatic support for prepareStatement naming (Postgres)
13
-
14
- ## Installation
15
-
16
- ```shell
17
- npm install @triptease/sql-template @triptease/sql-template-postgres
18
- ```
19
-
20
- ## Usage
21
-
22
- ```javascript
23
- import {SQL, id} from "@triptease/sql-template";
24
- import {statement} from "@triptease/sql-template-postgres";
25
-
26
- client.query(statement(SQL`select * from ${id(table)} where name = ${name}`));
27
- ```
28
-
29
- ## Cheatsheet
30
-
31
- ### Core (@triptease/sql-template)
32
-
33
- | function | Description |
34
- |------------------------------------------------|----------------------------------------------------------------------|
35
- | `SQL` | The main function to create tagged templates for SQL (*DB agnostic*) |
36
- | `text` (alias `raw`) | Input raw SQL without any escaping (*use with care*) |
37
- | `id` / `ids` | Input dynamic identifiers into SQL (*escaped as needed*) |
38
- | `value` (optional) / `values` (alias `spread`) | Input one or more values into SQL (*escaped as needed*) |
39
-
40
-
41
- ### Postgres (@triptease/sql-template-postgres)
42
-
43
- | function | Description |
44
- |------------------------------------------------|---------------------------------------------------------------------|
45
- | `statement` | Converts DB agnostic `SQL` template into postgres statement |
46
- | `prepareStatement` | Converts DB agnostic `SQL` template into postgres prepare statement |
47
- | `debugQuery` | Used to debug a query (*use with care*) |
48
-
49
- *use with care -> Used incorrectly you can open yourself up to SQL injection*
50
-
51
-
52
- ### Extending
53
-
54
- It is incredibly simple to extend to other DBs, have a look at the [postgres implementation](https://github.com/triptease/sql-template/blob/master/sql-template-postgres/src/index.ts#L17).
55
-
56
-
1
+ # Sql Template
2
+
3
+ This is yet another SQL tagged template for Typescript/Javascript
4
+
5
+ ## Why another library?
6
+
7
+ * Typescript first
8
+ * Functional/Immutable
9
+ * Super simple implementation (see [SQL.ts](https://github.com/triptease/sql-template/blob/master/sql-template/src/SQL.ts))
10
+ * Full escaping of identifiers and values
11
+ * Plugable to any DB (currently Postgres via `@triptease/sql-template-postgres` with `pg`)
12
+ * Automatic support for prepareStatement naming (Postgres)
13
+
14
+ ## Installation
15
+
16
+ ```shell
17
+ npm install @triptease/sql-template @triptease/sql-template-postgres
18
+ # or
19
+ bun add @triptease/sql-template @triptease/sql-template-postgres
20
+ ```
21
+
22
+ Both packages are plain ESM JavaScript with type declarations, so they work in Node and Bun.
23
+ `@triptease/sql-template` has no dependencies and `@triptease/sql-template-postgres` depends only on it
24
+ (it does not depend on `pg`; bring your own driver). In Node they can also be loaded with `require()`
25
+ (Node 20.19+/22.12+, which can `require()` ES modules).
26
+
27
+ ## Usage
28
+
29
+ ### Node with [pg](https://node-postgres.com/)
30
+
31
+ ```typescript
32
+ import pg from "pg";
33
+ import {SQL, id, values} from "@triptease/sql-template";
34
+ import {statement, prepareStatement} from "@triptease/sql-template-postgres";
35
+
36
+ const pool = new pg.Pool();
37
+ const table = 'users', name = "O'Brien";
38
+
39
+ await pool.query(statement(SQL`select * from ${id(table)} where name = ${name}`));
40
+ // { text: 'select * from "users" where name = $1', values: ["O'Brien"] }
41
+
42
+ // named prepared statement (the name defaults to a hash of the SQL)
43
+ await pool.query(prepareStatement(SQL`select * from users where id in (${values([1, 2, 3])})`));
44
+ // { name: 'feb13b21…', text: 'select * from users where id in ($1, $2, $3)', values: [1, 2, 3] }
45
+ ```
46
+
47
+ ### Bun
48
+
49
+ If you only target Bun, its built-in [`sql`](https://bun.com/docs/api/sql) tagged template already binds values
50
+ and escapes identifiers, so you probably don't need this library. Use it in Bun when your query code also has to
51
+ run on Node with `pg`, or when you want queries as plain immutable values you can build, test and log
52
+ (`debugQuery`) without a connection.
53
+
54
+ ### Composing
55
+
56
+ ```typescript
57
+ import {SQL, id, ids, text, values} from "@triptease/sql-template";
58
+
59
+ // templates nest, and values() can join them with an explicit (raw) separator
60
+ const filters = [SQL`${id('name')} = ${name}`, SQL`${id('age')} > ${30}`];
61
+ const query = SQL`select ${ids(['id', 'name'])} from users where ${values(filters, text(' and '))}`;
62
+ // select "id", "name" from users where "name" = $1 and "age" > $2
63
+ ```
64
+
65
+ Templates are immutable (frozen) and flat. Expressions are identified by a `Symbol.for` brand rather than
66
+ `instanceof`, so templates built by a different copy of `@triptease/sql-template` (e.g. two installed
67
+ versions) still work, while plain data (such as parsed JSON) can never be mistaken for SQL.
68
+
69
+ ## Cheatsheet
70
+
71
+ ### Core (@triptease/sql-template)
72
+
73
+ | function | Description |
74
+ |------------------------------------------------|---------------------------------------------------------------------------------------------|
75
+ | `SQL` | The main function to create tagged templates for SQL (*DB agnostic*) |
76
+ | `text` (alias `raw`) | Input raw SQL without any escaping (*use with care*) |
77
+ | `id` / `ids(names, separator?)` | Input dynamic identifiers into SQL (*escaped as needed*) |
78
+ | `value` (optional) / `values(list, separator?)` (alias `spread`) | Input one or more values into SQL (*bound as parameters*) |
79
+ | `template` | Combine expressions into a (flattened, frozen) template |
80
+ | `isExpression` / `isText` / `isIdentifier` / `isValue` / `isTemplate` / `kindOf` | Type guards based on the expression brand (for adapters) |
81
+
82
+ `separator` is an Expression and defaults to `text(', ')`.
83
+
84
+ ### Postgres (@triptease/sql-template-postgres)
85
+
86
+ | function | Description |
87
+ |------------------------------------------------|----------------------------------------------------------------------|
88
+ | `statement` | Converts DB agnostic `SQL` template into a postgres statement (`pg`) |
89
+ | `prepareStatement` | Converts DB agnostic `SQL` template into a named prepared statement (`pg`) |
90
+ | `escapeIdentifier` / `escapeLiteral` | Postgres identifier and string literal escaping (ported from `pg`) |
91
+ | `debugQuery` | Renders a template with values inlined, for debugging (*see below*) |
92
+
93
+ *use with care -> Used incorrectly you can open yourself up to SQL injection*
94
+
95
+ `debugQuery` escapes every value (strings, numbers, booleans, null, Dates, Buffers, arrays and objects), but
96
+ it is meant for logging: execute queries with `statement`/`prepareStatement` so values are bound as
97
+ parameters. Inlined literals are not always typed the way parameters are (e.g. arrays become array literal
98
+ strings and objects JSON strings, which need a cast in some contexts). Dates are inlined as UTC ISO strings, so for
99
+ `timestamp` (without time zone) or text targets the output only matches what `pg` executes when the process runs
100
+ with `TZ=UTC`.
101
+
102
+ ## Breaking changes (since the pg-only releases)
103
+
104
+ * The `separator` of `values`/`spread`/`ids` is now an Expression: replace `values(xs, ' or ')` with
105
+ `values(xs, text(' or '))`. Passing a string throws a `TypeError` (it used to be inserted as raw SQL).
106
+ * `@triptease/sql-template-postgres` no longer depends on `pg`; its `QueryConfig` is a local type
107
+ that is structurally compatible with pg's.
108
+ * Adapters throw on expressions they do not understand instead of silently dropping them, and SQL with an
109
+ invalid escape sequence (e.g. ``SQL`\u` ``) throws instead of producing the text `undefined`.
110
+ * Public types use `unknown` instead of `any`; `ids` takes `string[]`.
111
+ * The packages are ESM only (they used to be CommonJS):
112
+ * `require()` needs Node 20.19+/22.12+ (older Node versions can only `import` them).
113
+ * TypeScript consumers need `"module"`/`"moduleResolution"` set to `nodenext` (or `node20`) or `bundler`.
114
+ A CommonJS project using `node16` (or `nodenext` before TypeScript 5.8) gets error TS1479; switch to one of
115
+ those settings or load the packages with `import()`.
116
+ * Only the package roots are exported: deep imports such as `@triptease/sql-template/Text` fail with
117
+ `ERR_PACKAGE_PATH_NOT_EXPORTED`. Import everything from `@triptease/sql-template` /
118
+ `@triptease/sql-template-postgres`.
119
+ * `Expression` is abstract and subclassing it is not supported: an instance of your own subclass is rejected with
120
+ a `TypeError` (compose `SQL`/`text`/`id`/`value`/`template` instead).
121
+ * `debugQuery` output changed: `null`/`undefined` render as `NULL`, booleans as `TRUE`/`FALSE`, negative numbers
122
+ are parenthesised, `NaN`/`Infinity` are quoted, and Dates, Buffers, arrays and objects are rendered as escaped
123
+ literals. Do not parse or compare its output.
124
+ * A `Template` built directly with `new Template([...])` now keeps the values of nested templates (they used to be
125
+ dropped).
126
+
127
+ ## Extending
128
+
129
+ It is simple to extend to other DBs: switch on `kindOf(expression)` (`'text'`, `'identifier'`, `'value'`,
130
+ `'template'`) and throw on anything else. Have a look at the
131
+ [postgres implementation](https://github.com/triptease/sql-template/blob/master/sql-template-postgres/src/statement.ts).
132
+
133
+ ## Development
134
+
135
+ Tool versions (node, bun) are pinned in `mise.toml`. Install [mise](https://mise.jdx.dev/getting-started.html), then:
136
+
137
+ ```shell
138
+ mise install # installs the pinned node and bun
139
+ ./run # install deps, clean, build (tsc --build), typecheck and test (bun test)
140
+ ```
141
+
142
+ The repo is a bun workspace: each package has a published `src/package.json` (runtime dependencies only)
143
+ and a private `test/package.json` (test dependencies). Tests import the packages by name.
144
+
145
+ The postgres integration tests (`sql-template-postgres/test/integration.test.ts`) run real queries through
146
+ `pg`. They need a database:
147
+
148
+ ```shell
149
+ DATABASE_URL=postgres://postgres:postgres@localhost:5432/postgres ./run
150
+ ```
151
+
152
+ Without `DATABASE_URL` they are skipped locally (with a warning) but fail when `CI=true`; the GitHub Actions
153
+ test job provides a `postgres:18` service.
154
+
155
+ ## Releasing
156
+
157
+ Every push to `triptease/sql-template` that passes the `test` job runs the `publish` job in
158
+ [`.github/workflows/build.yml`](.github/workflows/build.yml), which runs `scripts/release.ts`:
159
+
160
+ * Version: `0.<git rev-list --count HEAD>.<GITHUB_RUN_NUMBER>`; dist-tag `latest` on `master`, `dev` on other branches.
161
+ * It does a clean `tsc --build` (without declaration maps, as the `.ts` sources are not published), then
162
+ writes `<pkg>/dist/package.json` from `<pkg>/src/package.json` (version
163
+ stamped, `workspace:*` replaced by that version, `main`/`types`/`exports` pointing at the `.js`/`.d.ts`
164
+ files, explicit `files`) and copies this README next to it. The `dist` directory is what gets published, so
165
+ import paths never contain `src/` or `dist/`. It fails if any `workspace:` range is left.
166
+ * It publishes core first, then postgres, with `npm publish <pkg>/dist`:
167
+ * to **GitHub Packages** (`npm.pkg.github.com`) on every run, using the workflow's `GITHUB_TOKEN`;
168
+ * to **npmjs** only when the repo variable `NPM_PUBLISH` is `true`, with `--access public --provenance`,
169
+ authenticated by npm Trusted Publishing (OIDC).
170
+ * Versions that are already on a registry are skipped, so a failed run can simply be re-run.
171
+ * The staged `dist` directories are kept as the `dist` artifact of the workflow run.
172
+
173
+ Check what would be published without uploading anything (`npm publish --dry-run`; add `NPM_PUBLISH=true`
174
+ to include npmjs):
175
+
176
+ ```shell
177
+ ./run release --dry-run
178
+ ```
179
+
180
+ `./run ci` runs the full build and tests, then the release script.
181
+
182
+ ### One-off setup (needs an npm org admin and a GitHub repo admin)
183
+
184
+ 1. On npmjs.com, for **each** of `@triptease/sql-template` and `@triptease/sql-template-postgres`:
185
+ Settings → Trusted Publisher → GitHub Actions, organization `triptease`, repository `sql-template`,
186
+ workflow filename `build.yml`, environment empty, allowed actions: `npm publish` only. The workflow
187
+ filename is part of the trust relationship, so do not rename `build.yml`. Publishing access is set to
188
+ "require two-factor authentication and disallow tokens", so only this workflow can publish.
189
+ 2. In the GitHub repo, set the Actions variable `NPM_PUBLISH` to `true` (Settings → Secrets and variables →
190
+ Actions → Variables). Until then npmjs publishing is skipped and only GitHub Packages is published.
191
+ 3. GitHub Packages: after the first publish, check both packages under the org's Packages page, make sure
192
+ they are linked to this repository and set their visibility to public if they should be visible
193
+ outside the org.
194
+
195
+ ### Installing from GitHub Packages
196
+
197
+ npmjs is the main channel. Installing from GitHub Packages needs authentication even for public packages:
198
+ a token with `read:packages` and an `.npmrc` like
199
+
200
+ ```ini
201
+ @triptease:registry=https://npm.pkg.github.com
202
+ //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
203
+ ```
package/SQL.d.ts CHANGED
@@ -1,2 +1,2 @@
1
- import { Template } from "./Template";
2
- export declare function SQL(chunks: TemplateStringsArray, ...values: any[]): Template;
1
+ import { Template } from "./Template.js";
2
+ export declare function SQL(chunks: TemplateStringsArray, ...values: unknown[]): Template;
package/SQL.js CHANGED
@@ -1,15 +1,14 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.SQL = void 0;
4
- const Template_1 = require("./Template");
5
- const Text_1 = require("./Text");
6
- const Value_1 = require("./Value");
7
- function SQL(chunks, ...values) {
8
- return (0, Template_1.template)(...chunks.flatMap((chunk, index) => {
1
+ import { template, Template } from "./Template.js";
2
+ import { text } from "./Text.js";
3
+ import { value } from "./Value.js";
4
+ export function SQL(chunks, ...values) {
5
+ return template(...chunks.flatMap((chunk, index) => {
6
+ if (typeof chunk !== 'string') {
7
+ throw new SyntaxError(`Invalid escape sequence in SQL template: ${JSON.stringify(chunks.raw?.[index])}`);
8
+ }
9
9
  if (index > (values.length - 1))
10
- return [(0, Text_1.text)(chunk)];
11
- return [(0, Text_1.text)(chunk), (0, Value_1.value)(values[index])];
10
+ return [text(chunk)];
11
+ return [text(chunk), value(values[index])];
12
12
  }));
13
13
  }
14
- exports.SQL = SQL;
15
14
  //# sourceMappingURL=SQL.js.map
package/SQL.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SQL.js","sourceRoot":"","sources":["../src/SQL.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,QAAQ,EAAE,QAAQ,EAAC,MAAM,eAAe,CAAC;AACjD,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AAC/B,OAAO,EAAC,KAAK,EAAC,MAAM,YAAY,CAAC;AAEjC,MAAM,UAAU,GAAG,CAAC,MAA4B,EAAE,GAAG,MAAiB;IAClE,OAAO,QAAQ,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;QAC/C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;YAC5B,MAAM,IAAI,WAAW,CAAC,4CAA4C,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC;QAC7G,CAAC;QACD,IAAI,KAAK,GAAG,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;QACtD,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC,CAAC;AACR,CAAC","sourcesContent":["import {template, Template} from \"./Template.js\";\nimport {text} from \"./Text.js\";\nimport {value} from \"./Value.js\";\n\nexport function SQL(chunks: TemplateStringsArray, ...values: unknown[]): Template {\n return template(...chunks.flatMap((chunk, index) => {\n if (typeof chunk !== 'string') {\n throw new SyntaxError(`Invalid escape sequence in SQL template: ${JSON.stringify(chunks.raw?.[index])}`);\n }\n if (index > (values.length - 1)) return [text(chunk)];\n return [text(chunk), value(values[index])];\n }));\n}\n"]}
package/Template.d.ts CHANGED
@@ -1,6 +1,15 @@
1
- import { Expression } from "./Expression";
1
+ import { Expression, kind } from "./Expression.js";
2
+ export interface Template {
3
+ readonly [kind]: 'template';
4
+ }
5
+ /**
6
+ * A sequence of expressions. Construction always normalises: nested templates are flattened
7
+ * (recursively) and empty text is removed, so adapters only ever see Text, Identifier and Value.
8
+ * Templates are frozen.
9
+ */
2
10
  export declare class Template extends Expression {
3
11
  readonly expressions: ReadonlyArray<Expression>;
4
12
  constructor(expressions: ReadonlyArray<Expression>);
5
13
  }
14
+ export declare function isTemplate(value: unknown): value is Template;
6
15
  export declare function template(...expressions: Expression[]): Template;
package/Template.js CHANGED
@@ -1,23 +1,38 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.template = exports.Template = void 0;
4
- const Expression_1 = require("./Expression");
5
- const Text_1 = require("./Text");
6
- class Template extends Expression_1.Expression {
1
+ import { Expression, isExpression, kind, kindOf } from "./Expression.js";
2
+ import { invalidExpression } from "./invalid.js";
3
+ function flatten(expressions, result) {
4
+ for (const e of expressions) {
5
+ if (!isExpression(e))
6
+ throw invalidExpression(e, 'Template');
7
+ const k = kindOf(e);
8
+ if (k === 'template')
9
+ flatten(e.expressions, result);
10
+ else if (k === 'text' && e.text === '')
11
+ continue;
12
+ else
13
+ result.push(e);
14
+ }
15
+ return result;
16
+ }
17
+ /**
18
+ * A sequence of expressions. Construction always normalises: nested templates are flattened
19
+ * (recursively) and empty text is removed, so adapters only ever see Text, Identifier and Value.
20
+ * Templates are frozen.
21
+ */
22
+ export class Template extends Expression {
23
+ expressions;
7
24
  constructor(expressions) {
8
25
  super();
9
- this.expressions = expressions;
26
+ this.expressions = Object.freeze(flatten(expressions, []));
27
+ if (new.target === Template)
28
+ Object.freeze(this);
10
29
  }
11
30
  }
12
- exports.Template = Template;
13
- function template(...expressions) {
14
- return new Template(expressions.flatMap(e => {
15
- if (e instanceof Text_1.Text && e.text === '')
16
- return [];
17
- if (e instanceof Template)
18
- return e.expressions;
19
- return [e];
20
- }));
31
+ Object.defineProperty(Template.prototype, kind, { value: 'template' });
32
+ export function isTemplate(value) {
33
+ return kindOf(value) === 'template';
34
+ }
35
+ export function template(...expressions) {
36
+ return new Template(expressions);
21
37
  }
22
- exports.template = template;
23
38
  //# sourceMappingURL=Template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Template.js","sourceRoot":"","sources":["../src/Template.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAC,MAAM,iBAAiB,CAAC;AACvE,OAAO,EAAC,iBAAiB,EAAC,MAAM,cAAc,CAAC;AAE/C,SAAS,OAAO,CAAC,WAA+B,EAAE,MAAoB;IAClE,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;QAC1B,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC;YAAE,MAAM,iBAAiB,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;QAC7D,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;QACpB,IAAI,CAAC,KAAK,UAAU;YAAE,OAAO,CAAE,CAAc,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC;aAC9D,IAAI,CAAC,KAAK,MAAM,IAAK,CAAwB,CAAC,IAAI,KAAK,EAAE;YAAE,SAAS;;YACpE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAMD;;;;GAIG;AACH,MAAM,OAAO,QAAS,SAAQ,UAAU;IAC3B,WAAW,CAA4B;IAEhD,YAAY,WAAsC;QAC9C,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC,CAAC;QAC3D,IAAI,IAAI,MAAM,KAAK,QAAQ;YAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACrD,CAAC;CACJ;AAED,MAAM,CAAC,cAAc,CAAC,QAAQ,CAAC,SAAS,EAAE,IAAI,EAAE,EAAC,KAAK,EAAE,UAAU,EAAC,CAAC,CAAC;AAErE,MAAM,UAAU,UAAU,CAAC,KAAc;IACrC,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,UAAU,CAAC;AACxC,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,GAAG,WAAyB;IACjD,OAAO,IAAI,QAAQ,CAAC,WAAW,CAAC,CAAC;AACrC,CAAC","sourcesContent":["import {Expression, isExpression, kind, kindOf} from \"./Expression.js\";\nimport {invalidExpression} from \"./invalid.js\";\n\nfunction flatten(expressions: readonly unknown[], result: Expression[]): Expression[] {\n for (const e of expressions) {\n if (!isExpression(e)) throw invalidExpression(e, 'Template');\n const k = kindOf(e);\n if (k === 'template') flatten((e as Template).expressions, result);\n else if (k === 'text' && (e as { text?: unknown }).text === '') continue;\n else result.push(e);\n }\n return result;\n}\n\nexport interface Template {\n readonly [kind]: 'template';\n}\n\n/**\n * A sequence of expressions. Construction always normalises: nested templates are flattened\n * (recursively) and empty text is removed, so adapters only ever see Text, Identifier and Value.\n * Templates are frozen.\n */\nexport class Template extends Expression {\n readonly expressions: ReadonlyArray<Expression>;\n\n constructor(expressions: ReadonlyArray<Expression>) {\n super();\n this.expressions = Object.freeze(flatten(expressions, []));\n if (new.target === Template) Object.freeze(this);\n }\n}\n\nObject.defineProperty(Template.prototype, kind, {value: 'template'});\n\nexport function isTemplate(value: unknown): value is Template {\n return kindOf(value) === 'template';\n}\n\nexport function template(...expressions: Expression[]): Template {\n return new Template(expressions);\n}\n"]}
package/Text.d.ts CHANGED
@@ -1,7 +1,13 @@
1
- import { Expression } from "./Expression";
1
+ import { Expression, kind } from "./Expression.js";
2
+ export interface Text {
3
+ readonly [kind]: 'text';
4
+ }
5
+ /** Raw SQL, inserted verbatim without any escaping. */
2
6
  export declare class Text extends Expression {
3
7
  readonly text: string;
4
8
  constructor(text: string);
5
9
  }
10
+ export declare function isText(value: unknown): value is Text;
11
+ /** Raw SQL, inserted verbatim without any escaping (*use with care*). */
6
12
  export declare function text(text: string): Text;
7
13
  export declare const raw: typeof text;
package/Text.js CHANGED
@@ -1,17 +1,23 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.raw = exports.text = exports.Text = void 0;
4
- const Expression_1 = require("./Expression");
5
- class Text extends Expression_1.Expression {
1
+ import { Expression, kind, kindOf } from "./Expression.js";
2
+ /** Raw SQL, inserted verbatim without any escaping. */
3
+ export class Text extends Expression {
4
+ text;
6
5
  constructor(text) {
7
6
  super();
7
+ if (typeof text !== 'string')
8
+ throw new TypeError(`Text must be a string but was ${typeof text}`);
8
9
  this.text = text;
10
+ if (new.target === Text)
11
+ Object.freeze(this);
9
12
  }
10
13
  }
11
- exports.Text = Text;
12
- function text(text) {
14
+ Object.defineProperty(Text.prototype, kind, { value: 'text' });
15
+ export function isText(value) {
16
+ return kindOf(value) === 'text';
17
+ }
18
+ /** Raw SQL, inserted verbatim without any escaping (*use with care*). */
19
+ export function text(text) {
13
20
  return new Text(text);
14
21
  }
15
- exports.text = text;
16
- exports.raw = text;
22
+ export const raw = text;
17
23
  //# sourceMappingURL=Text.js.map
package/Text.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Text.js","sourceRoot":"","sources":["../src/Text.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAE,IAAI,EAAE,MAAM,EAAC,MAAM,iBAAiB,CAAC;AAMzD,uDAAuD;AACvD,MAAM,OAAO,IAAK,SAAQ,UAAU;IACvB,IAAI,CAAS;IAEtB,YAAY,IAAY;QACpB,KAAK,EAAE,CAAC;QACR,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,MAAM,IAAI,SAAS,CAAC,iCAAiC,OAAO,IAAI,EAAE,CAAC,CAAC;QAClG,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,IAAI,MAAM,KAAK,IAAI;YAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACjD,CAAC;CACJ;AAED,MAAM,CAAC,cAAc,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,EAAC,KAAK,EAAE,MAAM,EAAC,CAAC,CAAC;AAE7D,MAAM,UAAU,MAAM,CAAC,KAAc;IACjC,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,MAAM,CAAC;AACpC,CAAC;AAED,yEAAyE;AACzE,MAAM,UAAU,IAAI,CAAC,IAAY;IAC7B,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAgB,IAAI,CAAC","sourcesContent":["import {Expression, kind, kindOf} from \"./Expression.js\";\n\nexport interface Text {\n readonly [kind]: 'text';\n}\n\n/** Raw SQL, inserted verbatim without any escaping. */\nexport class Text extends Expression {\n readonly text: string;\n\n constructor(text: string) {\n super();\n if (typeof text !== 'string') throw new TypeError(`Text must be a string but was ${typeof text}`);\n this.text = text;\n if (new.target === Text) Object.freeze(this);\n }\n}\n\nObject.defineProperty(Text.prototype, kind, {value: 'text'});\n\nexport function isText(value: unknown): value is Text {\n return kindOf(value) === 'text';\n}\n\n/** Raw SQL, inserted verbatim without any escaping (*use with care*). */\nexport function text(text: string): Text {\n return new Text(text);\n}\n\nexport const raw: typeof text = text;\n"]}
package/Value.d.ts CHANGED
@@ -1,9 +1,22 @@
1
- import { Expression } from "./Expression";
2
- import { Template } from "./Template";
1
+ import { Expression, kind } from "./Expression.js";
2
+ import { Template } from "./Template.js";
3
+ export interface Value {
4
+ readonly [kind]: 'value';
5
+ }
6
+ /** A value that adapters pass as a bound parameter. The wrapper is frozen; the wrapped value is not copied. */
3
7
  export declare class Value extends Expression {
4
- readonly value: any;
5
- constructor(value: any);
8
+ readonly value: unknown;
9
+ constructor(value: unknown);
6
10
  }
7
- export declare function value(value: any): Expression;
8
- export declare function values(values: any[], separator?: string): Template;
11
+ export declare function isValue(value: unknown): value is Value;
12
+ /**
13
+ * Wraps `value` as a bound Value. Expressions are passed through unchanged and `undefined` becomes `null`.
14
+ * Throws for an instance of a user subclass of Expression (subclassing Expression is not supported).
15
+ */
16
+ export declare function value(value: unknown): Expression;
17
+ /**
18
+ * Multiple values separated by `separator` (default `text(', ')`).
19
+ * The separator is an Expression so any raw SQL is explicit at the call site.
20
+ */
21
+ export declare function values(values: readonly unknown[], separator?: Expression): Template;
9
22
  export declare const spread: typeof values;
package/Value.js CHANGED
@@ -1,27 +1,41 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.spread = exports.values = exports.value = exports.Value = void 0;
4
- const Expression_1 = require("./Expression");
5
- const Template_1 = require("./Template");
6
- const Text_1 = require("./Text");
7
- class Value extends Expression_1.Expression {
1
+ import { Expression, isExpression, kind, kindOf } from "./Expression.js";
2
+ import { template, Template } from "./Template.js";
3
+ import { text } from "./Text.js";
4
+ import { invalidExpression } from "./invalid.js";
5
+ import { separated } from "./separated.js";
6
+ /** A value that adapters pass as a bound parameter. The wrapper is frozen; the wrapped value is not copied. */
7
+ export class Value extends Expression {
8
+ value;
8
9
  constructor(value) {
9
10
  super();
10
11
  this.value = value;
12
+ if (new.target === Value)
13
+ Object.freeze(this);
11
14
  }
12
15
  }
13
- exports.Value = Value;
14
- function value(value) {
15
- if (value instanceof Expression_1.Expression)
16
+ Object.defineProperty(Value.prototype, kind, { value: 'value' });
17
+ export function isValue(value) {
18
+ return kindOf(value) === 'value';
19
+ }
20
+ /**
21
+ * Wraps `value` as a bound Value. Expressions are passed through unchanged and `undefined` becomes `null`.
22
+ * Throws for an instance of a user subclass of Expression (subclassing Expression is not supported).
23
+ */
24
+ export function value(value) {
25
+ if (isExpression(value))
16
26
  return value;
27
+ if (value instanceof Expression)
28
+ throw invalidExpression(value, 'SQL');
17
29
  if (value === undefined)
18
30
  return new Value(null);
19
31
  return new Value(value);
20
32
  }
21
- exports.value = value;
22
- function values(values, separator = ', ') {
23
- return (0, Template_1.template)(...values.flatMap((v, i) => i > 0 ? [(0, Text_1.text)(separator), value(v)] : [value(v)]));
33
+ /**
34
+ * Multiple values separated by `separator` (default `text(', ')`).
35
+ * The separator is an Expression so any raw SQL is explicit at the call site.
36
+ */
37
+ export function values(values, separator = text(', ')) {
38
+ return template(...separated(values.map(value), separator));
24
39
  }
25
- exports.values = values;
26
- exports.spread = values;
40
+ export const spread = values;
27
41
  //# sourceMappingURL=Value.js.map
package/Value.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Value.js","sourceRoot":"","sources":["../src/Value.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAC,MAAM,iBAAiB,CAAC;AACvE,OAAO,EAAC,QAAQ,EAAE,QAAQ,EAAC,MAAM,eAAe,CAAC;AACjD,OAAO,EAAC,IAAI,EAAC,MAAM,WAAW,CAAC;AAC/B,OAAO,EAAC,iBAAiB,EAAC,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAMzC,+GAA+G;AAC/G,MAAM,OAAO,KAAM,SAAQ,UAAU;IACxB,KAAK,CAAU;IAExB,YAAY,KAAc;QACtB,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,IAAI,MAAM,KAAK,KAAK;YAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IAClD,CAAC;CACJ;AAED,MAAM,CAAC,cAAc,CAAC,KAAK,CAAC,SAAS,EAAE,IAAI,EAAE,EAAC,KAAK,EAAE,OAAO,EAAC,CAAC,CAAC;AAE/D,MAAM,UAAU,OAAO,CAAC,KAAc;IAClC,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,OAAO,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,KAAK,CAAC,KAAc;IAChC,IAAI,YAAY,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IACtC,IAAI,KAAK,YAAY,UAAU;QAAE,MAAM,iBAAiB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACvE,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,IAAI,KAAK,CAAC,IAAI,CAAC,CAAC;IAChD,OAAO,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC;AAC5B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,MAAM,CAAC,MAA0B,EAAE,SAAS,GAAe,IAAI,CAAC,IAAI,CAAC;IACjF,OAAO,QAAQ,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,CAAC,MAAM,MAAM,GAAkB,MAAM,CAAC","sourcesContent":["import {Expression, isExpression, kind, kindOf} from \"./Expression.js\";\nimport {template, Template} from \"./Template.js\";\nimport {text} from \"./Text.js\";\nimport {invalidExpression} from \"./invalid.js\";\nimport {separated} from \"./separated.js\";\n\nexport interface Value {\n readonly [kind]: 'value';\n}\n\n/** A value that adapters pass as a bound parameter. The wrapper is frozen; the wrapped value is not copied. */\nexport class Value extends Expression {\n readonly value: unknown;\n\n constructor(value: unknown) {\n super();\n this.value = value;\n if (new.target === Value) Object.freeze(this);\n }\n}\n\nObject.defineProperty(Value.prototype, kind, {value: 'value'});\n\nexport function isValue(value: unknown): value is Value {\n return kindOf(value) === 'value';\n}\n\n/**\n * Wraps `value` as a bound Value. Expressions are passed through unchanged and `undefined` becomes `null`.\n * Throws for an instance of a user subclass of Expression (subclassing Expression is not supported).\n */\nexport function value(value: unknown): Expression {\n if (isExpression(value)) return value;\n if (value instanceof Expression) throw invalidExpression(value, 'SQL');\n if (value === undefined) return new Value(null);\n return new Value(value);\n}\n\n/**\n * Multiple values separated by `separator` (default `text(', ')`).\n * The separator is an Expression so any raw SQL is explicit at the call site.\n */\nexport function values(values: readonly unknown[], separator: Expression = text(', ')): Template {\n return template(...separated(values.map(value), separator));\n}\n\nexport const spread: typeof values = values;\n"]}
package/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- export * from "./Expression";
2
- export * from "./Identifier";
3
- export * from "./SQL";
4
- export * from "./Template";
5
- export * from "./Text";
6
- export * from "./Value";
1
+ export * from "./Expression.js";
2
+ export * from "./Identifier.js";
3
+ export * from "./SQL.js";
4
+ export * from "./Template.js";
5
+ export * from "./Text.js";
6
+ export * from "./Value.js";
package/index.js CHANGED
@@ -1,10 +1,7 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- const tslib_1 = require("tslib");
4
- (0, tslib_1.__exportStar)(require("./Expression"), exports);
5
- (0, tslib_1.__exportStar)(require("./Identifier"), exports);
6
- (0, tslib_1.__exportStar)(require("./SQL"), exports);
7
- (0, tslib_1.__exportStar)(require("./Template"), exports);
8
- (0, tslib_1.__exportStar)(require("./Text"), exports);
9
- (0, tslib_1.__exportStar)(require("./Value"), exports);
1
+ export * from "./Expression.js";
2
+ export * from "./Identifier.js";
3
+ export * from "./SQL.js";
4
+ export * from "./Template.js";
5
+ export * from "./Text.js";
6
+ export * from "./Value.js";
10
7
  //# sourceMappingURL=index.js.map
package/index.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAA;AAC/B,cAAc,iBAAiB,CAAA;AAC/B,cAAc,UAAU,CAAA;AACxB,cAAc,eAAe,CAAA;AAC7B,cAAc,WAAW,CAAA;AACzB,cAAc,YAAY,CAAA","sourcesContent":["export * from \"./Expression.js\"\nexport * from \"./Identifier.js\"\nexport * from \"./SQL.js\"\nexport * from \"./Template.js\"\nexport * from \"./Text.js\"\nexport * from \"./Value.js\"\n\n\n"]}
package/invalid.d.ts ADDED
@@ -0,0 +1,6 @@
1
+ /**
2
+ * The error for something that is not a usable expression where one is required.
3
+ * An instance of a user subclass of Expression has no `kind` brand, so no adapter could render it:
4
+ * it is rejected rather than being bound as a value or silently dropped.
5
+ */
6
+ export declare function invalidExpression(value: unknown, where: string): TypeError;
package/invalid.js ADDED
@@ -0,0 +1,14 @@
1
+ import { Expression } from "./Expression.js";
2
+ /**
3
+ * The error for something that is not a usable expression where one is required.
4
+ * An instance of a user subclass of Expression has no `kind` brand, so no adapter could render it:
5
+ * it is rejected rather than being bound as a value or silently dropped.
6
+ */
7
+ export function invalidExpression(value, where) {
8
+ if (value instanceof Expression) {
9
+ return new TypeError(`${where}: ${value.constructor.name} extends Expression but is not a supported kind of expression. ` +
10
+ `Subclassing Expression is not supported: build expressions with SQL, text, id, value or template instead`);
11
+ }
12
+ return new TypeError(`${where} can only contain Expressions but got ${value === null ? 'null' : typeof value}`);
13
+ }
14
+ //# sourceMappingURL=invalid.js.map
package/invalid.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invalid.js","sourceRoot":"","sources":["../src/invalid.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAC,MAAM,iBAAiB,CAAC;AAE3C;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc,EAAE,KAAa;IAC3D,IAAI,KAAK,YAAY,UAAU,EAAE,CAAC;QAC9B,OAAO,IAAI,SAAS,CAAC,GAAG,KAAK,KAAK,KAAK,CAAC,WAAW,CAAC,IAAI,iEAAiE;YACrH,0GAA0G,CAAC,CAAC;IACpH,CAAC;IACD,OAAO,IAAI,SAAS,CAAC,GAAG,KAAK,yCAAyC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC;AACpH,CAAC","sourcesContent":["import {Expression} from \"./Expression.js\";\n\n/**\n * The error for something that is not a usable expression where one is required.\n * An instance of a user subclass of Expression has no `kind` brand, so no adapter could render it:\n * it is rejected rather than being bound as a value or silently dropped.\n */\nexport function invalidExpression(value: unknown, where: string): TypeError {\n if (value instanceof Expression) {\n return new TypeError(`${where}: ${value.constructor.name} extends Expression but is not a supported kind of expression. ` +\n `Subclassing Expression is not supported: build expressions with SQL, text, id, value or template instead`);\n }\n return new TypeError(`${where} can only contain Expressions but got ${value === null ? 'null' : typeof value}`);\n}\n"]}
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "@triptease/sql-template",
3
- "version": "0.24.25",
3
+ "version": "0.36.7",
4
+ "type": "module",
4
5
  "repository": {
5
6
  "type": "git",
6
- "url": "git+ssh://git@github.com/triptease/sql-template.git"
7
+ "url": "git+https://github.com/triptease/sql-template.git",
8
+ "directory": "sql-template/src"
7
9
  },
8
10
  "author": "Triptease",
9
11
  "license": "MIT",
@@ -11,17 +13,19 @@
11
13
  "url": "https://github.com/triptease/sql-template/issues"
12
14
  },
13
15
  "homepage": "https://github.com/triptease/sql-template/",
14
- "publishConfig": {
15
- "directory": "../dist",
16
- "access": "public"
16
+ "main": "./index.js",
17
+ "types": "./index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./index.d.ts",
21
+ "default": "./index.js"
22
+ },
23
+ "./package.json": "./package.json"
17
24
  },
18
25
  "files": [
19
- "*.js",
20
- "*.d.ts"
21
- ],
22
- "dependencies": {
23
- "tslib": "^2.3.1"
24
- },
25
- "scripts": {},
26
- "readme": "# Sql Template\r\n\r\nThis is yet another SQL tagged template for Typescript/Javascript\r\n\r\n## Why another library?\r\n\r\n* Typescript first\r\n* Functional/Immutable\r\n* Super simple implementation (the main SQL function is [3 lines of code](https://github.com/triptease/sql-template/blob/master/sql-template/src/SQL.ts#L5))\r\n* Full escaping of identifiers and values\r\n* Plugable to any DB (currently only Postgres @triptease\\sql-template-postgres )\r\n* Automatic support for prepareStatement naming (Postgres)\r\n\r\n## Installation\r\n\r\n```shell\r\nnpm install @triptease/sql-template @triptease/sql-template-postgres\r\n```\r\n\r\n## Usage\r\n\r\n```javascript\r\nimport {SQL, id} from \"@triptease/sql-template\";\r\nimport {statement} from \"@triptease/sql-template-postgres\";\r\n\r\nclient.query(statement(SQL`select * from ${id(table)} where name = ${name}`));\r\n```\r\n\r\n## Cheatsheet\r\n\r\n### Core (@triptease/sql-template) \r\n\r\n| function | Description |\r\n|------------------------------------------------|----------------------------------------------------------------------|\r\n| `SQL` | The main function to create tagged templates for SQL (*DB agnostic*) |\r\n| `text` (alias `raw`) | Input raw SQL without any escaping (*use with care*) |\r\n | `id` / `ids` | Input dynamic identifiers into SQL (*escaped as needed*) |\r\n | `value` (optional) / `values` (alias `spread`) | Input one or more values into SQL (*escaped as needed*) |\r\n\r\n\r\n### Postgres (@triptease/sql-template-postgres)\r\n\r\n| function | Description |\r\n|------------------------------------------------|---------------------------------------------------------------------|\r\n| `statement` | Converts DB agnostic `SQL` template into postgres statement |\r\n| `prepareStatement` | Converts DB agnostic `SQL` template into postgres prepare statement |\r\n| `debugQuery` | Used to debug a query (*use with care*) |\r\n\r\n*use with care -> Used incorrectly you can open yourself up to SQL injection*\r\n\r\n\r\n### Extending\r\n\r\nIt is incredibly simple to extend to other DBs, have a look at the [postgres implementation](https://github.com/triptease/sql-template/blob/master/sql-template-postgres/src/index.ts#L17). \r\n\r\n\r\n"
27
- }
26
+ "**/*.js",
27
+ "**/*.js.map",
28
+ "**/*.d.ts",
29
+ "README.md"
30
+ ]
31
+ }
package/separated.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { type Expression } from "./Expression.js";
2
+ /** @internal Interleaves `separator` between `expressions`. */
3
+ export declare function separated(expressions: readonly Expression[], separator: Expression): Expression[];
package/separated.js ADDED
@@ -0,0 +1,9 @@
1
+ import { isExpression } from "./Expression.js";
2
+ /** @internal Interleaves `separator` between `expressions`. */
3
+ export function separated(expressions, separator) {
4
+ if (!isExpression(separator)) {
5
+ throw new TypeError('separator must be an Expression, e.g. text(\', \') (raw strings are no longer accepted)');
6
+ }
7
+ return expressions.flatMap((e, i) => i > 0 ? [separator, e] : [e]);
8
+ }
9
+ //# sourceMappingURL=separated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"separated.js","sourceRoot":"","sources":["../src/separated.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,YAAY,EAAC,MAAM,iBAAiB,CAAC;AAE9D,+DAA+D;AAC/D,MAAM,UAAU,SAAS,CAAC,WAAkC,EAAE,SAAqB;IAC/E,IAAI,CAAC,YAAY,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,SAAS,CAAC,yFAAyF,CAAC,CAAC;IACnH,CAAC;IACD,OAAO,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACvE,CAAC","sourcesContent":["import {type Expression, isExpression} from \"./Expression.js\";\n\n/** @internal Interleaves `separator` between `expressions`. */\nexport function separated(expressions: readonly Expression[], separator: Expression): Expression[] {\n if (!isExpression(separator)) {\n throw new TypeError('separator must be an Expression, e.g. text(\\', \\') (raw strings are no longer accepted)');\n }\n return expressions.flatMap((e, i) => i > 0 ? [separator, e] : [e]);\n}\n"]}