foxhound 2.0.26 → 2.0.27

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
@@ -6,15 +6,15 @@ FoxHound is a database query builder that generates dialect-specific SQL from a
6
6
 
7
7
  ## Features
8
8
 
9
- - **Chainable API** every configuration method returns the query object for fluent composition
10
- - **Multiple Dialects** generate SQL for MySQL, PostgreSQL, MSSQL, SQLite, ALASQL, or plain English from the same code
11
- - **Parameterized Queries** user-supplied values are always bound as named parameters, preventing SQL injection
12
- - **Schema-Aware** automatic management of identity columns, timestamps, user stamps, and soft-delete tracking
13
- - **Full CRUD + Count** build CREATE, READ, UPDATE, DELETE, UNDELETE, and COUNT queries
14
- - **Query Overrides** underscore-style templates for custom SQL while retaining automatic parameter binding
15
- - **Filtering & Sorting** rich filter expressions with multiple operators, logical grouping, and multi-column sorting
16
- - **Joins & Pagination** INNER, LEFT, and custom joins plus dialect-aware LIMIT/OFFSET pagination
17
- - **Fable Integration** operates as a Fable service, inheriting configuration, logging, and UUID generation
9
+ - **Chainable API** -- every configuration method returns the query object for fluent composition
10
+ - **Multiple Dialects** -- generate SQL for MySQL, PostgreSQL, MSSQL, SQLite, ALASQL, or plain English from the same code
11
+ - **Parameterized Queries** -- user-supplied values are always bound as named parameters, preventing SQL injection
12
+ - **Schema-Aware** -- automatic management of identity columns, timestamps, user stamps, and soft-delete tracking
13
+ - **Full CRUD + Count** -- build CREATE, READ, UPDATE, DELETE, UNDELETE, and COUNT queries
14
+ - **Query Overrides** -- underscore-style templates for custom SQL while retaining automatic parameter binding
15
+ - **Filtering & Sorting** -- rich filter expressions with multiple operators, logical grouping, and multi-column sorting
16
+ - **Joins & Pagination** -- INNER, LEFT, and custom joins plus dialect-aware LIMIT/OFFSET pagination
17
+ - **Fable Integration** -- operates as a Fable service, inheriting configuration, logging, and UUID generation
18
18
 
19
19
  ## Quick Start
20
20
 
@@ -55,14 +55,14 @@ FoxHound follows a configure-then-build pattern. You create a query instance, c
55
55
  ```
56
56
  Application Code
57
57
  └── FoxHound Query
58
- ├── setScope('Books') target table
59
- ├── addFilter('Genre', '...') WHERE clause
60
- ├── addSort('Title') ORDER BY clause
61
- ├── setCap(25) LIMIT clause
62
- ├── setDialect('MySQL') output format
63
- └── buildReadQuery() SQL generation
64
- ├── query.body SQL string
65
- └── query.parameters bound values
58
+ ├── setScope('Books') -> target table
59
+ ├── addFilter('Genre', '...') -> WHERE clause
60
+ ├── addSort('Title') -> ORDER BY clause
61
+ ├── setCap(25) -> LIMIT clause
62
+ ├── setDialect('MySQL') -> output format
63
+ └── buildReadQuery() -> SQL generation
64
+ ├── query.body -> SQL string
65
+ └── query.parameters -> bound values
66
66
  ```
67
67
 
68
68
  ## Dialects
@@ -83,14 +83,14 @@ When a schema is attached, FoxHound automatically manages special columns:
83
83
 
84
84
  | Type | Description |
85
85
  |------|-------------|
86
- | `AutoIdentity` | Auto-increment primary key `NULL` on insert, skipped on update |
86
+ | `AutoIdentity` | Auto-increment primary key -- `NULL` on insert, skipped on update |
87
87
  | `AutoGUID` | Automatically generated UUID on insert |
88
88
  | `CreateDate` / `CreateIDUser` | Auto-populated on insert only |
89
89
  | `UpdateDate` / `UpdateIDUser` | Auto-populated on insert and update |
90
90
  | `DeleteDate` / `DeleteIDUser` | Auto-populated on soft delete |
91
- | `Deleted` | Soft-delete flag auto-filtered in reads |
92
- | `JSON` | Structured JSON data serialized to `TEXT` on write, parsed on read |
93
- | `JSONProxy` | JSON stored in a different SQL column uses `StorageColumn` for SQL, virtual name for objects |
91
+ | `Deleted` | Soft-delete flag -- auto-filtered in reads |
92
+ | `JSON` | Structured JSON data -- serialized to `TEXT` on write, parsed on read |
93
+ | `JSONProxy` | JSON stored in a different SQL column -- uses `StorageColumn` for SQL, virtual name for objects |
94
94
 
95
95
  ## Filter Operators
96
96
 
@@ -125,7 +125,7 @@ npm run docker-dev-build
125
125
  npm run docker-dev-run
126
126
  ```
127
127
 
128
- 3. Open http://localhost:24238/ the password is "retold"
128
+ 3. Open http://localhost:24238/ -- the password is "retold"
129
129
 
130
130
  ## Documentation
131
131
 
package/docs/README.md CHANGED
@@ -6,15 +6,15 @@ FoxHound is a fluent query generation DSL for Node.js and the browser. It produ
6
6
 
7
7
  ## Features
8
8
 
9
- - **Chainable API** every configuration method returns the query object, so you can compose queries in a single expression
10
- - **Multiple Dialects** generate SQL for MySQL, PostgreSQL, Microsoft SQL Server, SQLite, ALASQL, or plain English, all from the same code
11
- - **Parameterized Queries** user-supplied values are always bound as named parameters, preventing SQL injection
12
- - **Schema-Aware** when a schema is provided, FoxHound automatically manages identity columns, timestamps, user stamps, and soft-delete tracking
13
- - **Full CRUD + Count** build CREATE, READ, UPDATE, DELETE, UNDELETE, and COUNT queries
14
- - **Query Overrides** supply an underscore-style template to customize query generation while still benefiting from automatic parameter binding
15
- - **Filtering & Sorting** rich filter expressions with multiple operators, logical grouping, and multi-column sorting
16
- - **Joins & Pagination** INNER, LEFT, and custom joins plus dialect-aware LIMIT/OFFSET pagination
17
- - **Fable Integration** operates as a Fable service, inheriting configuration, logging, and UUID generation
9
+ - **Chainable API** -- every configuration method returns the query object, so you can compose queries in a single expression
10
+ - **Multiple Dialects** -- generate SQL for MySQL, PostgreSQL, Microsoft SQL Server, SQLite, ALASQL, or plain English, all from the same code
11
+ - **Parameterized Queries** -- user-supplied values are always bound as named parameters, preventing SQL injection
12
+ - **Schema-Aware** -- when a schema is provided, FoxHound automatically manages identity columns, timestamps, user stamps, and soft-delete tracking
13
+ - **Full CRUD + Count** -- build CREATE, READ, UPDATE, DELETE, UNDELETE, and COUNT queries
14
+ - **Query Overrides** -- supply an underscore-style template to customize query generation while still benefiting from automatic parameter binding
15
+ - **Filtering & Sorting** -- rich filter expressions with multiple operators, logical grouping, and multi-column sorting
16
+ - **Joins & Pagination** -- INNER, LEFT, and custom joins plus dialect-aware LIMIT/OFFSET pagination
17
+ - **Fable Integration** -- operates as a Fable service, inheriting configuration, logging, and UUID generation
18
18
 
19
19
  ## Quick Start
20
20
 
@@ -48,24 +48,24 @@ console.log(tmpQuery.query.parameters);
48
48
 
49
49
  ## How It Works
50
50
 
51
- 1. **Create a Query** instantiate via `foxhound.new(fable)` or through a Fable service
52
- 2. **Configure** chain methods to set scope (table), fields, filters, sorts, joins, and pagination
53
- 3. **Set a Dialect** call `.setDialect('MySQL')` (or PostgreSQL, MSSQL, SQLite, ALASQL, English)
54
- 4. **Build** call a build method such as `.buildReadQuery()` to generate the SQL
55
- 5. **Execute** read the generated SQL from `.query.body` and bound parameters from `.query.parameters`
51
+ 1. **Create a Query** -- instantiate via `foxhound.new(fable)` or through a Fable service
52
+ 2. **Configure** -- chain methods to set scope (table), fields, filters, sorts, joins, and pagination
53
+ 3. **Set a Dialect** -- call `.setDialect('MySQL')` (or PostgreSQL, MSSQL, SQLite, ALASQL, English)
54
+ 4. **Build** -- call a build method such as `.buildReadQuery()` to generate the SQL
55
+ 5. **Execute** -- read the generated SQL from `.query.body` and bound parameters from `.query.parameters`
56
56
 
57
57
  ## Documentation
58
58
 
59
- - [Quickstart](quickstart.md) get up and running in five minutes
60
- - [Architecture](architecture.md) understand FoxHound's internal design
61
- - [Filters](filters.md) filter operators and logical grouping
62
- - [Sorting](sorting.md) ORDER BY clause generation
63
- - [Joins](joins.md) multi-table queries
64
- - [Pagination](pagination.md) LIMIT/OFFSET across dialects
65
- - [Schema Integration](schema.md) automatic column management
66
- - [Dialects](dialects/README.md) dialect-specific features and differences
67
- - [API Reference](api/README.md) complete function reference with code examples
68
- - [Query Overrides](query-overrides.md) custom SQL templates
59
+ - [Quickstart](quickstart.md) -- get up and running in five minutes
60
+ - [Architecture](architecture.md) -- understand FoxHound's internal design
61
+ - [Filters](filters.md) -- filter operators and logical grouping
62
+ - [Sorting](sorting.md) -- ORDER BY clause generation
63
+ - [Joins](joins.md) -- multi-table queries
64
+ - [Pagination](pagination.md) -- LIMIT/OFFSET across dialects
65
+ - [Schema Integration](schema.md) -- automatic column management
66
+ - [Dialects](dialects/README.md) -- dialect-specific features and differences
67
+ - [API Reference](api/README.md) -- complete function reference with code examples
68
+ - [Query Overrides](query-overrides.md) -- custom SQL templates
69
69
 
70
70
  ## Related Packages
71
71
 
package/docs/_cover.md CHANGED
@@ -1,4 +1,4 @@
1
- # FoxHound <small>2</small>
1
+ # FoxHound
2
2
 
3
3
  > A fluent query generation DSL for Node.js and the browser
4
4
 
@@ -0,0 +1,7 @@
1
+ {
2
+ "Name": "foxhound",
3
+ "Version": "2.0.26",
4
+ "Description": "A Database Query generation library.",
5
+ "GeneratedAt": "2026-04-10T17:20:13.929Z",
6
+ "GitCommit": "9fe56f5"
7
+ }
@@ -120,4 +120,4 @@ tmpQuery
120
120
  .setDisableDeleteTracking(true);
121
121
  ```
122
122
 
123
- This gives you full manual control over all column values useful for data migration or import scenarios.
123
+ This gives you full manual control over all column values -- useful for data migration or import scenarios.
@@ -89,7 +89,7 @@ Generate a soft-delete UPDATE or a hard DELETE.
89
89
  When a schema has a `Deleted` column type and delete tracking is enabled:
90
90
 
91
91
  ```javascript
92
- // Soft delete generates an UPDATE
92
+ // Soft delete -- generates an UPDATE
93
93
  tmpQuery.buildDeleteQuery();
94
94
  // => UPDATE `Books` SET Deleted = 1, DeleteDate = NOW(3), ...
95
95
  ```
@@ -97,7 +97,7 @@ tmpQuery.buildDeleteQuery();
97
97
  When no schema or delete tracking is disabled:
98
98
 
99
99
  ```javascript
100
- // Hard delete generates a DELETE
100
+ // Hard delete -- generates a DELETE
101
101
  tmpQuery.setDisableDeleteTracking(true).buildDeleteQuery();
102
102
  // => DELETE FROM `Books` WHERE IDBook = :IDBook_w0;
103
103
  ```
package/docs/api/clone.md CHANGED
@@ -18,7 +18,7 @@ Returns a new FoxHound query instance with copies of the current scope, begin, c
18
18
 
19
19
  ### Description
20
20
 
21
- Cloning is useful when you need to build multiple similar queries from the same base configuration. The cloned query has its own independent state changes to the clone do not affect the original.
21
+ Cloning is useful when you need to build multiple similar queries from the same base configuration. The cloned query has its own independent state -- changes to the clone do not affect the original.
22
22
 
23
23
  ### Example
24
24
 
@@ -20,7 +20,7 @@ Returns `this` for chaining.
20
20
 
21
21
  ## Description
22
22
 
23
- The scope defines which table (or collection, in NoSQL dialects) the query targets. This is required for all query types it appears as the `FROM` clause in SELECT, `INSERT INTO` in CREATE, `UPDATE` in UPDATE, and `DELETE FROM` in DELETE.
23
+ The scope defines which table (or collection, in NoSQL dialects) the query targets. This is required for all query types -- it appears as the `FROM` clause in SELECT, `INSERT INTO` in CREATE, `UPDATE` in UPDATE, and `DELETE FROM` in DELETE.
24
24
 
25
25
  FoxHound validates that the input is a string. Non-string values are ignored and an error is logged.
26
26
 
@@ -168,10 +168,10 @@ tmpQuery
168
168
 
169
169
  FoxHound depends on Fable for:
170
170
 
171
- - **UUID Generation** each query and record gets a unique identifier via `_Fable.getUUID()`
172
- - **Logging** filter, scope, and query errors are logged through `_Fable.log`
173
- - **Utility Functions** `_Fable.Utility.extend()` for parameter merging and `_Fable.Utility.template()` for query overrides
174
- - **Configuration** inherits any relevant settings from the Fable config
171
+ - **UUID Generation** -- each query and record gets a unique identifier via `_Fable.getUUID()`
172
+ - **Logging** -- filter, scope, and query errors are logged through `_Fable.log`
173
+ - **Utility Functions** -- `_Fable.Utility.extend()` for parameter merging and `_Fable.Utility.template()` for query overrides
174
+ - **Configuration** -- inherits any relevant settings from the Fable config
175
175
 
176
176
  ## Schema-Aware Generation
177
177
 
@@ -1,73 +1,327 @@
1
1
  /* ============================================================================
2
- Pict Docuserve - Base Styles
2
+ Pict Docuserve - Base Styles & Theme Variables
3
3
  ============================================================================ */
4
4
 
5
- /* Reset and base */
6
- *, *::before, *::after {
5
+ /* ----------------------------------------------------------------------------
6
+ Theme variables light defaults on :root.
7
+ Dark mode applies when either:
8
+ (a) the user explicitly selected dark via <html data-theme="dark">
9
+ (b) the user hasn't chosen anything AND the system prefers dark
10
+ An explicit <html data-theme="light"> pins the light palette regardless.
11
+ ---------------------------------------------------------------------------- */
12
+
13
+ :root
14
+ {
15
+ /* Surfaces */
16
+ --docuserve-bg: #FDFBF7;
17
+ --docuserve-bg-elevated: #FFFFFF;
18
+ --docuserve-border: #DDD6CA;
19
+ --docuserve-border-soft: #EAE3D8;
20
+
21
+ /* Text */
22
+ --docuserve-text: #2A241E;
23
+ --docuserve-text-strong: #3D3229;
24
+ --docuserve-text-muted: #5E5549;
25
+ --docuserve-text-dim: #8A7F72;
26
+
27
+ /* Accent / links */
28
+ --docuserve-accent: #2E7D74;
29
+ --docuserve-accent-hover: #236660;
30
+
31
+ /* Top bar */
32
+ --docuserve-topbar-bg: #3D3229;
33
+ --docuserve-topbar-text: #E8E0D4;
34
+ --docuserve-topbar-text-muted: #B5AA9A;
35
+ --docuserve-topbar-text-dim: #8A7F72;
36
+ --docuserve-topbar-hover-bg: #524438;
37
+ --docuserve-topbar-version-bg: rgba(255, 255, 255, 0.06);
38
+ --docuserve-topbar-version-border: rgba(255, 255, 255, 0.08);
39
+ --docuserve-topbar-version-text: #B5AA9A;
40
+
41
+ /* Sidebar */
42
+ --docuserve-sidebar-bg: #FAF7F1;
43
+ --docuserve-sidebar-border: #DDD6CA;
44
+ --docuserve-sidebar-border-soft: #E5DED1;
45
+ --docuserve-sidebar-text: #423D37;
46
+ --docuserve-sidebar-group-title: #3D3229;
47
+ --docuserve-sidebar-module-text: #5E5549;
48
+ --docuserve-sidebar-hover-bg: #EAE3D8;
49
+ --docuserve-sidebar-hover-text: #2E7D74;
50
+ --docuserve-sidebar-active-bg: #E5DED1;
51
+ --docuserve-sidebar-active-text: #2E7D74;
52
+ --docuserve-sidebar-search-bg: #FFFFFF;
53
+ --docuserve-sidebar-search-border: #DDD6CA;
54
+
55
+ /* Inline code */
56
+ --docuserve-inline-code-bg: #F0ECE4;
57
+ --docuserve-inline-code-text: #9E3A50;
58
+
59
+ /* Code block panel */
60
+ --docuserve-code-bg: #F6F3EE;
61
+ --docuserve-code-border: #E5DED1;
62
+ --docuserve-code-gutter-bg: #EFEAE0;
63
+ --docuserve-code-gutter-border: #DDD6CA;
64
+ --docuserve-code-gutter-text: #A59986;
65
+ --docuserve-code-text: #2A241E;
66
+
67
+ /* Syntax tokens — low-chroma dark-on-light palette */
68
+ --docuserve-tok-keyword: #A03472;
69
+ --docuserve-tok-string: #1A6640;
70
+ --docuserve-tok-number: #B25A00;
71
+ --docuserve-tok-comment: #8A7F72;
72
+ --docuserve-tok-operator: #2E7D74;
73
+ --docuserve-tok-punctuation: #2A241E;
74
+ --docuserve-tok-function: #2A5DB0;
75
+ --docuserve-tok-property: #9E3A50;
76
+ --docuserve-tok-tag: #9E3A50;
77
+ --docuserve-tok-attr-name: #B25A00;
78
+ --docuserve-tok-attr-value: #1A6640;
79
+
80
+ /* Tables, blockquotes, mermaid */
81
+ --docuserve-table-header-bg: #F5F0E8;
82
+ --docuserve-table-row-alt-bg: #F9F6F0;
83
+ --docuserve-blockquote-bg: #F7F5F0;
84
+ --docuserve-blockquote-border: #2E7D74;
85
+ --docuserve-blockquote-text: #5E5549;
86
+ --docuserve-mermaid-bg: #FFFFFF;
87
+
88
+ /* Scrollbars */
89
+ --docuserve-scrollbar-track: #F5F0E8;
90
+ --docuserve-scrollbar-thumb: #D4CCBE;
91
+ --docuserve-scrollbar-thumb-hover: #B5AA9A;
92
+ }
93
+
94
+ @media (prefers-color-scheme: dark)
95
+ {
96
+ :root:not([data-theme="light"])
97
+ {
98
+ --docuserve-bg: #15120F;
99
+ --docuserve-bg-elevated: #1B1814;
100
+ --docuserve-border: #2F2823;
101
+ --docuserve-border-soft: #26211C;
102
+
103
+ --docuserve-text: #E8E0D4;
104
+ --docuserve-text-strong: #F2ECE0;
105
+ --docuserve-text-muted: #B5AA9A;
106
+ --docuserve-text-dim: #7A6F62;
107
+
108
+ --docuserve-accent: #5DB8A8;
109
+ --docuserve-accent-hover: #7FCCB8;
110
+
111
+ --docuserve-topbar-bg: #1A1612;
112
+ --docuserve-topbar-text: #E8E0D4;
113
+ --docuserve-topbar-text-muted: #B5AA9A;
114
+ --docuserve-topbar-text-dim: #7A6F62;
115
+ --docuserve-topbar-hover-bg: #2A241E;
116
+ --docuserve-topbar-version-bg: rgba(255, 255, 255, 0.05);
117
+ --docuserve-topbar-version-border: rgba(255, 255, 255, 0.09);
118
+ --docuserve-topbar-version-text: #B5AA9A;
119
+
120
+ --docuserve-sidebar-bg: #1B1814;
121
+ --docuserve-sidebar-border: #2F2823;
122
+ --docuserve-sidebar-border-soft: #26211C;
123
+ --docuserve-sidebar-text: #C9C0B3;
124
+ --docuserve-sidebar-group-title: #F2ECE0;
125
+ --docuserve-sidebar-module-text: #B5AA9A;
126
+ --docuserve-sidebar-hover-bg: #2A241E;
127
+ --docuserve-sidebar-hover-text: #7FCCB8;
128
+ --docuserve-sidebar-active-bg: #2F2823;
129
+ --docuserve-sidebar-active-text: #7FCCB8;
130
+ --docuserve-sidebar-search-bg: #26211C;
131
+ --docuserve-sidebar-search-border: #2F2823;
132
+
133
+ --docuserve-inline-code-bg: #2A241E;
134
+ --docuserve-inline-code-text: #E8B07A;
135
+
136
+ --docuserve-code-bg: #1E1A17;
137
+ --docuserve-code-border: #2F2823;
138
+ --docuserve-code-gutter-bg: #17130F;
139
+ --docuserve-code-gutter-border: #2F2823;
140
+ --docuserve-code-gutter-text: #6A6052;
141
+ --docuserve-code-text: #E8E0D4;
142
+
143
+ --docuserve-tok-keyword: #C678DD;
144
+ --docuserve-tok-string: #98C379;
145
+ --docuserve-tok-number: #D19A66;
146
+ --docuserve-tok-comment: #7F848E;
147
+ --docuserve-tok-operator: #56B6C2;
148
+ --docuserve-tok-punctuation: #E8E0D4;
149
+ --docuserve-tok-function: #61AFEF;
150
+ --docuserve-tok-property: #E06C75;
151
+ --docuserve-tok-tag: #E06C75;
152
+ --docuserve-tok-attr-name: #D19A66;
153
+ --docuserve-tok-attr-value: #98C379;
154
+
155
+ --docuserve-table-header-bg: #26211C;
156
+ --docuserve-table-row-alt-bg: #1F1B17;
157
+ --docuserve-blockquote-bg: #1F1B17;
158
+ --docuserve-blockquote-border: #5DB8A8;
159
+ --docuserve-blockquote-text: #C9C0B3;
160
+ --docuserve-mermaid-bg: #E8E0D4;
161
+
162
+ --docuserve-scrollbar-track: #1B1814;
163
+ --docuserve-scrollbar-thumb: #3A322B;
164
+ --docuserve-scrollbar-thumb-hover: #524438;
165
+ }
166
+ }
167
+
168
+ :root[data-theme="dark"]
169
+ {
170
+ --docuserve-bg: #15120F;
171
+ --docuserve-bg-elevated: #1B1814;
172
+ --docuserve-border: #2F2823;
173
+ --docuserve-border-soft: #26211C;
174
+
175
+ --docuserve-text: #E8E0D4;
176
+ --docuserve-text-strong: #F2ECE0;
177
+ --docuserve-text-muted: #B5AA9A;
178
+ --docuserve-text-dim: #7A6F62;
179
+
180
+ --docuserve-accent: #5DB8A8;
181
+ --docuserve-accent-hover: #7FCCB8;
182
+
183
+ --docuserve-topbar-bg: #1A1612;
184
+ --docuserve-topbar-text: #E8E0D4;
185
+ --docuserve-topbar-text-muted: #B5AA9A;
186
+ --docuserve-topbar-text-dim: #7A6F62;
187
+ --docuserve-topbar-hover-bg: #2A241E;
188
+ --docuserve-topbar-version-bg: rgba(255, 255, 255, 0.05);
189
+ --docuserve-topbar-version-border: rgba(255, 255, 255, 0.09);
190
+ --docuserve-topbar-version-text: #B5AA9A;
191
+
192
+ --docuserve-sidebar-bg: #1B1814;
193
+ --docuserve-sidebar-border: #2F2823;
194
+ --docuserve-sidebar-border-soft: #26211C;
195
+ --docuserve-sidebar-text: #C9C0B3;
196
+ --docuserve-sidebar-group-title: #F2ECE0;
197
+ --docuserve-sidebar-module-text: #B5AA9A;
198
+ --docuserve-sidebar-hover-bg: #2A241E;
199
+ --docuserve-sidebar-hover-text: #7FCCB8;
200
+ --docuserve-sidebar-active-bg: #2F2823;
201
+ --docuserve-sidebar-active-text: #7FCCB8;
202
+ --docuserve-sidebar-search-bg: #26211C;
203
+ --docuserve-sidebar-search-border: #2F2823;
204
+
205
+ --docuserve-inline-code-bg: #2A241E;
206
+ --docuserve-inline-code-text: #E8B07A;
207
+
208
+ --docuserve-code-bg: #1E1A17;
209
+ --docuserve-code-border: #2F2823;
210
+ --docuserve-code-gutter-bg: #17130F;
211
+ --docuserve-code-gutter-border: #2F2823;
212
+ --docuserve-code-gutter-text: #6A6052;
213
+ --docuserve-code-text: #E8E0D4;
214
+
215
+ --docuserve-tok-keyword: #C678DD;
216
+ --docuserve-tok-string: #98C379;
217
+ --docuserve-tok-number: #D19A66;
218
+ --docuserve-tok-comment: #7F848E;
219
+ --docuserve-tok-operator: #56B6C2;
220
+ --docuserve-tok-punctuation: #E8E0D4;
221
+ --docuserve-tok-function: #61AFEF;
222
+ --docuserve-tok-property: #E06C75;
223
+ --docuserve-tok-tag: #E06C75;
224
+ --docuserve-tok-attr-name: #D19A66;
225
+ --docuserve-tok-attr-value: #98C379;
226
+
227
+ --docuserve-table-header-bg: #26211C;
228
+ --docuserve-table-row-alt-bg: #1F1B17;
229
+ --docuserve-blockquote-bg: #1F1B17;
230
+ --docuserve-blockquote-border: #5DB8A8;
231
+ --docuserve-blockquote-text: #C9C0B3;
232
+ --docuserve-mermaid-bg: #E8E0D4;
233
+
234
+ --docuserve-scrollbar-track: #1B1814;
235
+ --docuserve-scrollbar-thumb: #3A322B;
236
+ --docuserve-scrollbar-thumb-hover: #524438;
237
+ }
238
+
239
+ /* ----------------------------------------------------------------------------
240
+ Reset and base
241
+ ---------------------------------------------------------------------------- */
242
+
243
+ *, *::before, *::after
244
+ {
7
245
  box-sizing: border-box;
8
246
  }
9
247
 
10
- html, body {
248
+ html, body
249
+ {
11
250
  margin: 0;
12
251
  padding: 0;
13
252
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
14
253
  font-size: 16px;
15
254
  line-height: 1.5;
16
- color: #423D37;
17
- background-color: #fff;
255
+ color: var(--docuserve-text);
256
+ background-color: var(--docuserve-bg);
18
257
  -webkit-font-smoothing: antialiased;
19
258
  -moz-osx-font-smoothing: grayscale;
259
+ transition: background-color 0.15s ease, color 0.15s ease;
20
260
  }
21
261
 
22
262
  /* Typography */
23
- h1, h2, h3, h4, h5, h6 {
263
+ h1, h2, h3, h4, h5, h6
264
+ {
24
265
  margin-top: 0;
25
266
  line-height: 1.3;
267
+ color: var(--docuserve-text-strong);
26
268
  }
27
269
 
28
- a {
29
- color: #2E7D74;
270
+ a
271
+ {
272
+ color: var(--docuserve-accent);
30
273
  text-decoration: none;
31
274
  }
32
275
 
33
- a:hover {
34
- color: #256861;
276
+ a:hover
277
+ {
278
+ color: var(--docuserve-accent-hover);
35
279
  }
36
280
 
37
281
  /* Application container */
38
- #Docuserve-Application-Container {
282
+ #Docuserve-Application-Container
283
+ {
39
284
  min-height: 100vh;
40
285
  }
41
286
 
42
287
  /* Utility: scrollbar styling */
43
- ::-webkit-scrollbar {
288
+ ::-webkit-scrollbar
289
+ {
44
290
  width: 8px;
291
+ height: 8px;
45
292
  }
46
293
 
47
- ::-webkit-scrollbar-track {
48
- background: #F5F0E8;
294
+ ::-webkit-scrollbar-track
295
+ {
296
+ background: var(--docuserve-scrollbar-track);
49
297
  }
50
298
 
51
- ::-webkit-scrollbar-thumb {
52
- background: #D4CCBE;
299
+ ::-webkit-scrollbar-thumb
300
+ {
301
+ background: var(--docuserve-scrollbar-thumb);
53
302
  border-radius: 4px;
54
303
  }
55
304
 
56
- ::-webkit-scrollbar-thumb:hover {
57
- background: #B5AA9A;
305
+ ::-webkit-scrollbar-thumb:hover
306
+ {
307
+ background: var(--docuserve-scrollbar-thumb-hover);
58
308
  }
59
309
 
60
310
  /* Responsive adjustments */
61
- @media (max-width: 768px) {
62
- html {
311
+ @media (max-width: 768px)
312
+ {
313
+ html
314
+ {
63
315
  font-size: 14px;
64
316
  }
65
317
 
66
- #Docuserve-Sidebar-Container {
318
+ #Docuserve-Sidebar-Container
319
+ {
67
320
  display: none;
68
321
  }
69
322
 
70
- .docuserve-body {
323
+ .docuserve-body
324
+ {
71
325
  flex-direction: column;
72
326
  }
73
327
  }
@@ -45,10 +45,10 @@ Each method receives the full Parameters object and returns a SQL string (or `fa
45
45
 
46
46
  All SQL dialects share these behaviors:
47
47
 
48
- - **Parameterized values** user data is always bound as named parameters, never interpolated
49
- - **Schema-aware column management** AutoIdentity, timestamps, user stamps, and soft-delete columns are handled automatically based on schema type annotations
50
- - **Soft-delete filtering** Read and Count queries automatically exclude rows where the `Deleted` column is `1` (when schema is present)
51
- - **Query overrides** Read and Count queries support underscore templates for custom SQL generation
48
+ - **Parameterized values** -- user data is always bound as named parameters, never interpolated
49
+ - **Schema-aware column management** -- AutoIdentity, timestamps, user stamps, and soft-delete columns are handled automatically based on schema type annotations
50
+ - **Soft-delete filtering** -- Read and Count queries automatically exclude rows where the `Deleted` column is `1` (when schema is present)
51
+ - **Query overrides** -- Read and Count queries support underscore templates for custom SQL generation
52
52
 
53
53
  ## Choosing a Dialect
54
54
 
@@ -56,7 +56,7 @@ INSERT INTO "Books" ( "IDBook", "Title") VALUES ( DEFAULT, :Title_1) RETURNING *
56
56
 
57
57
  ## RETURNING Clause
58
58
 
59
- All INSERT statements include `RETURNING *`, which returns the full inserted row including any auto-generated values like serial IDs and default timestamps.
59
+ All INSERT statements include `RETURNING *`, which returns the full inserted row -- including any auto-generated values like serial IDs and default timestamps.
60
60
 
61
61
  ## Joins
62
62
 
@@ -10,7 +10,7 @@ SQLite uses backtick quoting for identifiers to avoid conflicts with SQLite's ma
10
10
  SELECT * FROM Books WHERE `Genre` = :Genre_w0;
11
11
  ```
12
12
 
13
- Table names are not quoted in the SQLite dialect they are used as plain identifiers.
13
+ Table names are not quoted in the SQLite dialect -- they are used as plain identifiers.
14
14
 
15
15
  ## Named Parameters
16
16
 
@@ -49,7 +49,7 @@ SELECT COUNT(DISTINCT IDBook) AS RowCount FROM Books;
49
49
 
50
50
  ## Soft Delete
51
51
 
52
- Works the same as other dialects when a `Deleted` column is present in the schema, Delete generates an UPDATE:
52
+ Works the same as other dialects -- when a `Deleted` column is present in the schema, Delete generates an UPDATE:
53
53
 
54
54
  ```sql
55
55
  UPDATE Books SET `Deleted` = 1, `DeleteDate` = NOW(),
@@ -63,9 +63,9 @@ The `NOW()` calls are replaced with `datetime('now')` by the Meadow SQLite provi
63
63
 
64
64
  When using the Meadow SQLite provider with `better-sqlite3`:
65
65
 
66
- - **Boolean coercion** `better-sqlite3` only accepts numbers, strings, bigints, buffers, and null. The provider automatically converts boolean values (`true`/`false`) to integers (`1`/`0`)
67
- - **Undefined coercion** undefined values are converted to `null`
68
- - **Synchronous execution** `better-sqlite3` is synchronous, but the Meadow provider wraps calls in an async-compatible callback pattern
66
+ - **Boolean coercion** -- `better-sqlite3` only accepts numbers, strings, bigints, buffers, and null. The provider automatically converts boolean values (`true`/`false`) to integers (`1`/`0`)
67
+ - **Undefined coercion** -- undefined values are converted to `null`
68
+ - **Synchronous execution** -- `better-sqlite3` is synchronous, but the Meadow provider wraps calls in an async-compatible callback pattern
69
69
 
70
70
  ## Query Overrides
71
71
 
package/docs/index.html CHANGED
@@ -4,9 +4,9 @@
4
4
  <meta charset="utf-8">
5
5
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
7
- <meta name="description" content="Documentation powered by pict-docuserve">
7
+ <meta name="description" content="FoxHound v2.0.26 Documentation A Database Query generation library.">
8
8
 
9
- <title>Documentation</title>
9
+ <title>FoxHound v2.0.26 Documentation</title>
10
10
 
11
11
  <!-- Application Stylesheet -->
12
12
  <link href="css/docuserve.css" rel="stylesheet">
package/docs/joins.md CHANGED
@@ -94,8 +94,8 @@ Invalid joins are logged as warnings and silently skipped.
94
94
 
95
95
  ## Dialect Differences
96
96
 
97
- - **MySQL** `INNER JOIN Authors ON Authors.IDAuthor = Books.IDAuthor`
98
- - **MSSQL** `INNER JOIN [Authors] ON Authors.IDAuthor = Books.IDAuthor`
99
- - **SQLite/ALASQL** joins are supported in Read queries but not generated (the SQLite and ALASQL dialects do not include a `generateJoins` function; joins work through query overrides)
97
+ - **MySQL** -- `INNER JOIN Authors ON Authors.IDAuthor = Books.IDAuthor`
98
+ - **MSSQL** -- `INNER JOIN [Authors] ON Authors.IDAuthor = Books.IDAuthor`
99
+ - **SQLite/ALASQL** -- joins are supported in Read queries but not generated (the SQLite and ALASQL dialects do not include a `generateJoins` function; joins work through query overrides)
100
100
 
101
101
  > **Note:** The SQLite and ALASQL dialects are primarily designed for simpler single-table queries. For complex join scenarios, consider using a query override or the MySQL/MSSQL dialect.
@@ -8,9 +8,9 @@ FoxHound builds queries through a two-phase process: **configure** then **build*
8
8
  Configure ──► Build ──► Access Results
9
9
  ```
10
10
 
11
- 1. **Configure** set the scope, fields, filters, sorts, joins, pagination, records, and dialect
12
- 2. **Build** call one of the `build*Query()` methods to generate the SQL
13
- 3. **Access** read `query.body` for the SQL string and `query.parameters` for bound values
11
+ 1. **Configure** -- set the scope, fields, filters, sorts, joins, pagination, records, and dialect
12
+ 2. **Build** -- call one of the `build*Query()` methods to generate the SQL
13
+ 3. **Access** -- read `query.body` for the SQL string and `query.parameters` for bound values
14
14
 
15
15
  ## Creating a Query Instance
16
16
 
@@ -84,7 +84,7 @@ After a query is built, you can reset the parameters for reuse or clone the quer
84
84
  // Reset to default parameters
85
85
  tmpQuery.resetParameters();
86
86
 
87
- // Clone copies scope, begin, cap, schema, filters, sorts, and dataElements
87
+ // Clone -- copies scope, begin, cap, schema, filters, sorts, and dataElements
88
88
  var tmpClone = tmpQuery.clone();
89
89
  ```
90
90
 
@@ -60,7 +60,7 @@ tmpQuery.setDisableDeleteTracking(true); // Include delete columns on insert
60
60
 
61
61
  The INSERT syntax is largely the same across dialects, with a few differences:
62
62
 
63
- - **MySQL** uses backtick-quoted identifiers and `:name` parameters
64
- - **MSSQL** uses bracket-quoted identifiers, `@name` parameters, and skips the AutoIdentity column entirely (rather than inserting NULL)
65
- - **SQLite** uses backtick-quoted identifiers and `:name` parameters
66
- - **ALASQL** same as SQLite
63
+ - **MySQL** -- uses backtick-quoted identifiers and `:name` parameters
64
+ - **MSSQL** -- uses bracket-quoted identifiers, `@name` parameters, and skips the AutoIdentity column entirely (rather than inserting NULL)
65
+ - **SQLite** -- uses backtick-quoted identifiers and `:name` parameters
66
+ - **ALASQL** -- same as SQLite
@@ -27,13 +27,13 @@ When a schema is present, FoxHound manages certain columns automatically:
27
27
 
28
28
  | Schema Type | Behavior on Update |
29
29
  |------------|-------------------|
30
- | `AutoIdentity` | **Skipped** never updated |
31
- | `CreateDate` | **Skipped** set only on insert |
32
- | `CreateIDUser` | **Skipped** set only on insert |
30
+ | `AutoIdentity` | **Skipped** -- never updated |
31
+ | `CreateDate` | **Skipped** -- set only on insert |
32
+ | `CreateIDUser` | **Skipped** -- set only on insert |
33
33
  | `UpdateDate` | Set to current timestamp automatically |
34
34
  | `UpdateIDUser` | Set to the value from `setIDUser()` |
35
- | `DeleteDate` | **Skipped** managed by delete operations |
36
- | `DeleteIDUser` | **Skipped** managed by delete operations |
35
+ | `DeleteDate` | **Skipped** -- managed by delete operations |
36
+ | `DeleteIDUser` | **Skipped** -- managed by delete operations |
37
37
 
38
38
  ## Disabling Auto-Management
39
39
 
@@ -44,13 +44,13 @@ tmpQuery.setDisableAutoUserStamp(true); // Don't auto-set UpdateIDUser
44
44
 
45
45
  ## Important Notes
46
46
 
47
- - The record passed to `addRecord()` should contain only the columns you want to change FoxHound generates SET clauses for each key in the record object
47
+ - The record passed to `addRecord()` should contain only the columns you want to change -- FoxHound generates SET clauses for each key in the record object
48
48
  - Always include a filter (usually on the primary key) to avoid updating all rows
49
49
  - If the record object is empty or no records have been added, `buildUpdateQuery()` returns `false` for the query body
50
50
 
51
51
  ## Dialect Differences
52
52
 
53
- - **MySQL** backtick-quoted identifiers, `:name` parameters
54
- - **MSSQL** bracket-quoted identifiers, `@name` parameters; special handling for `UpdateDate` with `disableAutoDateStamp`
55
- - **SQLite** backtick-quoted identifiers, `:name` parameters
56
- - **ALASQL** same as SQLite
53
+ - **MySQL** -- backtick-quoted identifiers, `:name` parameters
54
+ - **MSSQL** -- bracket-quoted identifiers, `@name` parameters; special handling for `UpdateDate` with `disableAutoDateStamp`
55
+ - **SQLite** -- backtick-quoted identifiers, `:name` parameters
56
+ - **ALASQL** -- same as SQLite
@@ -85,4 +85,4 @@ Query overrides are useful when you need:
85
85
  - `UNION` queries
86
86
  - Any SQL feature not directly supported by FoxHound's fluent API
87
87
 
88
- For straightforward CRUD operations, the standard query builders are preferred they are safer and more portable across dialects.
88
+ For straightforward CRUD operations, the standard query builders are preferred -- they are safer and more portable across dialects.
@@ -189,8 +189,8 @@ console.log(tmpQuery.query.body);
189
189
 
190
190
  ## Next Steps
191
191
 
192
- - [Architecture](architecture.md) understand FoxHound's internal design
193
- - [Filters](filters.md) learn about filter operators and grouping
194
- - [Schema Integration](schema.md) use schemas for automatic column management
195
- - [Dialects](dialects/README.md) explore dialect-specific features
196
- - [API Reference](api/README.md) complete function reference
192
+ - [Architecture](architecture.md) -- understand FoxHound's internal design
193
+ - [Filters](filters.md) -- learn about filter operators and grouping
194
+ - [Schema Integration](schema.md) -- use schemas for automatic column management
195
+ - [Dialects](dialects/README.md) -- explore dialect-specific features
196
+ - [API Reference](api/README.md) -- complete function reference
@@ -1,5 +1,5 @@
1
1
  {
2
- "Generated": "2026-03-03T14:33:09.873Z",
2
+ "Generated": "2026-04-10T17:20:13.644Z",
3
3
  "GitHubOrg": "stevenvelozo",
4
4
  "DefaultBranch": "master",
5
5
  "Groups": [
@@ -1,5 +1,5 @@
1
1
  {
2
- "Generated": "2026-03-03T14:33:09.982Z",
2
+ "Generated": "2026-04-10T17:20:13.902Z",
3
3
  "DocumentCount": 27,
4
4
  "LunrIndex": {
5
5
  "version": "2.3.9",
package/docs/schema.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Schema Integration
2
2
 
3
- FoxHound is schema-aware when a schema array is attached to a query, it uses the column type annotations to automatically manage identity columns, timestamps, user stamps, and soft-delete tracking.
3
+ FoxHound is schema-aware -- when a schema array is attached to a query, it uses the column type annotations to automatically manage identity columns, timestamps, user stamps, and soft-delete tracking.
4
4
 
5
5
  ## Attaching a Schema
6
6
 
@@ -29,22 +29,22 @@ tmpQuery.query.schema = [
29
29
 
30
30
  | Type | Purpose | Create | Read | Update | Delete | Undelete |
31
31
  |------|---------|--------|------|--------|--------|----------|
32
- | `AutoIdentity` | Auto-increment primary key | `NULL` (DB assigns) | included | **skipped** | | |
33
- | `AutoGUID` | Auto-generated UUID | UUID or user value | included | included | | |
34
- | `CreateDate` | Row creation timestamp | `NOW()` | included | **skipped** | | |
35
- | `CreateIDUser` | Row creator user ID | `IDUser` | included | **skipped** | | |
32
+ | `AutoIdentity` | Auto-increment primary key | `NULL` (DB assigns) | included | **skipped** | -- | -- |
33
+ | `AutoGUID` | Auto-generated UUID | UUID or user value | included | included | -- | -- |
34
+ | `CreateDate` | Row creation timestamp | `NOW()` | included | **skipped** | -- | -- |
35
+ | `CreateIDUser` | Row creator user ID | `IDUser` | included | **skipped** | -- | -- |
36
36
  | `UpdateDate` | Last modification timestamp | `NOW()` | included | `NOW()` | `NOW()` | `NOW()` |
37
- | `UpdateIDUser` | Last modifier user ID | `IDUser` | included | `IDUser` | | `IDUser` |
38
- | `Deleted` | Soft-delete flag | `0` | auto-filtered | | set to `1` | set to `0` |
39
- | `DeleteDate` | Deletion timestamp | **skipped** | included | **skipped** | `NOW()` | |
40
- | `DeleteIDUser` | Deleter user ID | **skipped** | included | **skipped** | `IDUser` | |
41
- | `String` | Text data | parameterized | included | parameterized | | |
42
- | `Integer` | Numeric data | parameterized | included | parameterized | | |
43
- | `Decimal` | Decimal data | parameterized | included | parameterized | | |
44
- | `Boolean` | Boolean data | parameterized | included | parameterized | | |
45
- | `DateTime` | Date/time data | parameterized | included | parameterized | | |
46
- | `JSON` | Structured JSON data | `JSON.stringify` | included | `JSON.stringify` | | |
47
- | `JSONProxy` | JSON with different SQL column name | `JSON.stringify` to `StorageColumn` | included | `JSON.stringify` to `StorageColumn` | | |
37
+ | `UpdateIDUser` | Last modifier user ID | `IDUser` | included | `IDUser` | -- | `IDUser` |
38
+ | `Deleted` | Soft-delete flag | `0` | auto-filtered | -- | set to `1` | set to `0` |
39
+ | `DeleteDate` | Deletion timestamp | **skipped** | included | **skipped** | `NOW()` | -- |
40
+ | `DeleteIDUser` | Deleter user ID | **skipped** | included | **skipped** | `IDUser` | -- |
41
+ | `String` | Text data | parameterized | included | parameterized | -- | -- |
42
+ | `Integer` | Numeric data | parameterized | included | parameterized | -- | -- |
43
+ | `Decimal` | Decimal data | parameterized | included | parameterized | -- | -- |
44
+ | `Boolean` | Boolean data | parameterized | included | parameterized | -- | -- |
45
+ | `DateTime` | Date/time data | parameterized | included | parameterized | -- | -- |
46
+ | `JSON` | Structured JSON data | `JSON.stringify` | included | `JSON.stringify` | -- | -- |
47
+ | `JSONProxy` | JSON with different SQL column name | `JSON.stringify` to `StorageColumn` | included | `JSON.stringify` to `StorageColumn` | -- | -- |
48
48
 
49
49
  ## JSON and JSON Proxy Types
50
50
 
@@ -99,33 +99,33 @@ Nested paths are supported (e.g., `Metadata.dimensions.width`). JSON Proxy colum
99
99
 
100
100
  ### Create (INSERT)
101
101
 
102
- - `AutoIdentity` inserts `NULL` (MySQL/SQLite) or is omitted (MSSQL)
103
- - `AutoGUID` generates a UUID via Fable, unless the record has a valid GUID already
104
- - `CreateDate`, `UpdateDate` inserts the current timestamp
105
- - `CreateIDUser`, `UpdateIDUser` inserts the user ID from `setIDUser()`
106
- - `DeleteDate`, `DeleteIDUser` **skipped** (when delete tracking is enabled)
102
+ - `AutoIdentity` -> inserts `NULL` (MySQL/SQLite) or is omitted (MSSQL)
103
+ - `AutoGUID` -> generates a UUID via Fable, unless the record has a valid GUID already
104
+ - `CreateDate`, `UpdateDate` -> inserts the current timestamp
105
+ - `CreateIDUser`, `UpdateIDUser` -> inserts the user ID from `setIDUser()`
106
+ - `DeleteDate`, `DeleteIDUser` -> **skipped** (when delete tracking is enabled)
107
107
 
108
108
  ### Update
109
109
 
110
- - `AutoIdentity`, `CreateDate`, `CreateIDUser`, `DeleteDate`, `DeleteIDUser` **skipped**
111
- - `UpdateDate` set to current timestamp automatically
112
- - `UpdateIDUser` set to the value from `setIDUser()`
113
- - All other columns parameterized from the record
110
+ - `AutoIdentity`, `CreateDate`, `CreateIDUser`, `DeleteDate`, `DeleteIDUser` -> **skipped**
111
+ - `UpdateDate` -> set to current timestamp automatically
112
+ - `UpdateIDUser` -> set to the value from `setIDUser()`
113
+ - All other columns -> parameterized from the record
114
114
 
115
115
  ### Delete (Soft)
116
116
 
117
117
  Only these columns are modified:
118
- - `Deleted` set to `1`
119
- - `DeleteDate` set to current timestamp
120
- - `UpdateDate` set to current timestamp
121
- - `DeleteIDUser` set to the value from `setIDUser()`
118
+ - `Deleted` -> set to `1`
119
+ - `DeleteDate` -> set to current timestamp
120
+ - `UpdateDate` -> set to current timestamp
121
+ - `DeleteIDUser` -> set to the value from `setIDUser()`
122
122
 
123
123
  ### Undelete
124
124
 
125
125
  Only these columns are modified:
126
- - `Deleted` set to `0`
127
- - `UpdateDate` set to current timestamp
128
- - `UpdateIDUser` set to the value from `setIDUser()`
126
+ - `Deleted` -> set to `0`
127
+ - `UpdateDate` -> set to current timestamp
128
+ - `UpdateIDUser` -> set to the value from `setIDUser()`
129
129
 
130
130
  ### Read / Count
131
131
 
package/docs/sorting.md CHANGED
@@ -33,7 +33,7 @@ tmpQuery
33
33
  // ORDER BY Genre, PublishedYear DESC
34
34
  ```
35
35
 
36
- Columns without an explicit `Direction` (or with `Direction: 'Ascending'`) sort in ascending order the SQL default.
36
+ Columns without an explicit `Direction` (or with `Direction: 'Ascending'`) sort in ascending order -- the SQL default.
37
37
 
38
38
  ## Setting Sorts Directly
39
39
 
@@ -71,9 +71,9 @@ tmpQuery.setSort({Column: 'Title', Direction: 'Descending'});
71
71
 
72
72
  The `ORDER BY` clause syntax is consistent across all SQL dialects. The main difference is in identifier quoting:
73
73
 
74
- - **MySQL** `ORDER BY PublishedYear DESC`
75
- - **MSSQL** `ORDER BY [PublishedYear] DESC`
76
- - **SQLite/ALASQL** `ORDER BY \`PublishedYear\` DESC`
74
+ - **MySQL** -- `ORDER BY PublishedYear DESC`
75
+ - **MSSQL** -- `ORDER BY [PublishedYear] DESC`
76
+ - **SQLite/ALASQL** -- `ORDER BY \`PublishedYear\` DESC`
77
77
 
78
78
  ## Interaction with Pagination
79
79
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foxhound",
3
- "version": "2.0.26",
3
+ "version": "2.0.27",
4
4
  "description": "A Database Query generation library.",
5
5
  "main": "source/FoxHound.js",
6
6
  "scripts": {
@@ -48,7 +48,8 @@
48
48
  },
49
49
  "homepage": "https://github.com/stevenvelozo/foxhound",
50
50
  "devDependencies": {
51
- "quackage": "^1.0.63"
51
+ "pict-docuserve": "^0.1.5",
52
+ "quackage": "^1.1.0"
52
53
  },
53
54
  "dependencies": {
54
55
  "fable": "^3.1.63",
@@ -179,6 +179,51 @@ var FoxHoundDialectMSSQL = function(pFable)
179
179
  return tmpFieldList;
180
180
  };
181
181
 
182
+ /**
183
+ * Generate a field list for the outer SELECT of the legacy pagination
184
+ * wrapper. The outer FROM is a subquery aliased as [_Paged], so the
185
+ * default "[Table].*" qualifier can't resolve there — we need either
186
+ * an explicit column list from the schema or a bare "*".
187
+ *
188
+ * If the caller set explicit dataElements, reuse them (they reference
189
+ * bare column names, which work fine against the subquery alias).
190
+ * Otherwise emit an explicit list from the schema to keep [_RowNum]
191
+ * from leaking. As a last resort, fall back to "*" — callers without
192
+ * a schema will see [_RowNum] as an extra property on marshalled
193
+ * records but the query itself remains valid.
194
+ *
195
+ * @param: {Object} pParameters SQL Query Parameters
196
+ * @return: {String} Field list (prefixed with a single leading space)
197
+ */
198
+ var generateOuterFieldListForLegacyPagination = function(pParameters)
199
+ {
200
+ var tmpDataElements = pParameters.dataElements;
201
+ if (Array.isArray(tmpDataElements) && tmpDataElements.length > 0)
202
+ {
203
+ // Reuse the caller-supplied list. It emits unqualified column
204
+ // names ([Col], [Col] AS [Alias]) which resolve fine against
205
+ // the subquery alias.
206
+ return generateFieldList(pParameters);
207
+ }
208
+
209
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
210
+ if (tmpSchema.length > 0)
211
+ {
212
+ var tmpList = ' ';
213
+ for (var i = 0; i < tmpSchema.length; i++)
214
+ {
215
+ if (i > 0) tmpList += ', ';
216
+ tmpList += generateSafeFieldName(tmpSchema[i].Column);
217
+ }
218
+ return tmpList;
219
+ }
220
+
221
+ // No schema, no explicit dataElements — "*" is the best we can do.
222
+ // [_RowNum] will surface on marshalled records; downstream code can
223
+ // ignore it. Schemas are the norm via Meadow so this is rare.
224
+ return ' *';
225
+ };
226
+
182
227
  /**
183
228
  * Ensure a field name is properly escaped.
184
229
  */
@@ -352,12 +397,41 @@ var FoxHoundDialectMSSQL = function(pFable)
352
397
  return tmpWhere;
353
398
  };
354
399
 
400
+ /**
401
+ * Find the table's AutoIdentity primary-key column from the schema, if any.
402
+ * Used as a deterministic default ORDER BY when the caller didn't set a
403
+ * sort — MSSQL pagination (both OFFSET/FETCH and ROW_NUMBER) requires an
404
+ * ORDER BY clause or it produces a syntax error.
405
+ *
406
+ * @param: {Object} pParameters SQL Query Parameters
407
+ * @return: {String|null} The column name, or null if none found
408
+ */
409
+ var findPrimaryKeyColumn = function(pParameters)
410
+ {
411
+ var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
412
+ for (var i = 0; i < tmpSchema.length; i++)
413
+ {
414
+ if (tmpSchema[i].Type === 'AutoIdentity')
415
+ {
416
+ return tmpSchema[i].Column;
417
+ }
418
+ }
419
+ return null;
420
+ };
421
+
355
422
  /**
356
423
  * Generate an ORDER BY clause from the sort array
357
424
  *
358
425
  * Each entry in the sort is an object like:
359
426
  * {Column:'Color',Direction:'Descending'}
360
427
  *
428
+ * When no sort is specified but the query has a cap (pagination is
429
+ * active), inject a default ORDER BY on the primary key so MSSQL
430
+ * doesn't reject the OFFSET/FETCH or ROW_NUMBER clause. Without a
431
+ * schema the PK can't be inferred — fall back to `ORDER BY (SELECT 1)`
432
+ * which is legal for OFFSET/FETCH but not for ROW_NUMBER (the legacy
433
+ * pagination path handles that case by refusing to paginate).
434
+ *
361
435
  * @method: generateOrderBy
362
436
  * @param: {Object} pParameters SQL Query Parameters
363
437
  * @return: {String} Returns the field list clause
@@ -367,6 +441,15 @@ var FoxHoundDialectMSSQL = function(pFable)
367
441
  var tmpOrderBy = pParameters.sort;
368
442
  if (!Array.isArray(tmpOrderBy) || tmpOrderBy.length < 1)
369
443
  {
444
+ if (pParameters.cap)
445
+ {
446
+ var tmpPK = findPrimaryKeyColumn(pParameters);
447
+ if (tmpPK)
448
+ {
449
+ return ' ORDER BY ['+tmpPK+']';
450
+ }
451
+ return ' ORDER BY (SELECT 1)';
452
+ }
370
453
  return '';
371
454
  }
372
455
 
@@ -390,6 +473,12 @@ var FoxHoundDialectMSSQL = function(pFable)
390
473
  /**
391
474
  * Generate the limit clause
392
475
  *
476
+ * When `legacyPagination` is set on pParameters the limit is emitted
477
+ * by the Read function using a ROW_NUMBER() subquery wrapper instead
478
+ * (OFFSET/FETCH NEXT requires SQL Server 2012+ / compatibility level
479
+ * 110+, which some customers don't have). In that case this function
480
+ * returns an empty string.
481
+ *
393
482
  * @method: generateLimit
394
483
  * @param: {Object} pParameters SQL Query Parameters
395
484
  * @return: {String} Returns the table limit clause
@@ -401,6 +490,13 @@ var FoxHoundDialectMSSQL = function(pFable)
401
490
  return '';
402
491
  }
403
492
 
493
+ if (pParameters.legacyPagination)
494
+ {
495
+ // The Read function wraps the query in a ROW_NUMBER() subquery
496
+ // instead of appending an OFFSET/FETCH tail clause.
497
+ return '';
498
+ }
499
+
404
500
  var tmpLimit = ' OFFSET ';
405
501
  // If there is a begin record, we'll pass that in as well.
406
502
  if (pParameters.begin !== false)
@@ -1037,6 +1133,30 @@ var FoxHoundDialectMSSQL = function(pFable)
1037
1133
  }
1038
1134
  }
1039
1135
 
1136
+ // Legacy pagination path — emit a ROW_NUMBER() wrapper instead of
1137
+ // OFFSET/FETCH. Required for SQL Server 2008 R2 and earlier, or
1138
+ // for databases running at a compatibility level below 110 (2012).
1139
+ // Enabled via pParameters.legacyPagination (forwarded from the
1140
+ // meadow-connection-mssql provider's LegacyPagination config).
1141
+ if (pParameters.legacyPagination && pParameters.cap)
1142
+ {
1143
+ var tmpBegin = (pParameters.begin !== false) ? pParameters.begin : 0;
1144
+ var tmpEnd = tmpBegin + pParameters.cap;
1145
+ // generateOrderBy always returns a usable ORDER BY when cap is
1146
+ // set. ROW_NUMBER()'s OVER() clause takes the same body but
1147
+ // without the leading space.
1148
+ var tmpOverClause = tmpOrderBy.replace(/^ /, '');
1149
+ // The outer SELECT's FROM is the subquery alias, not the base
1150
+ // table — so the default field list's "[Table].*" qualifier
1151
+ // won't resolve at the outer level. Compute an outer field
1152
+ // list that works regardless of whether the caller supplied
1153
+ // explicit dataElements or relied on the default.
1154
+ var tmpOuterFieldList = generateOuterFieldListForLegacyPagination(pParameters);
1155
+ // INDEX hints and JOINs live on the inner select (they apply
1156
+ // to the base table). [_RowNum] is confined to the subquery.
1157
+ return `SELECT${tmpOptDistinct}${tmpOuterFieldList} FROM (SELECT${tmpFieldList}, ROW_NUMBER() OVER (${tmpOverClause}) AS [_RowNum] FROM${tmpTableName}${tmpIndexHints}${tmpJoin}${tmpWhere}) AS [_Paged] WHERE [_RowNum] > ${tmpBegin} AND [_RowNum] <= ${tmpEnd};`;
1158
+ }
1159
+
1040
1160
  return `SELECT${tmpOptDistinct}${tmpFieldList} FROM${tmpTableName}${tmpIndexHints}${tmpJoin}${tmpWhere}${tmpOrderBy}${tmpLimit};`;
1041
1161
  };
1042
1162
 
@@ -618,7 +618,7 @@ suite
618
618
  // This is the query generated by the MSSQL dialect
619
619
  _Fable.log.trace('Select Query', tmpQuery.query);
620
620
  Expect(tmpQuery.query.body)
621
- .to.equal('SELECT [Name], [Age], [Cost] FROM [Animal] WHERE [Deleted] = @Deleted_w0 OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY;');
621
+ .to.equal('SELECT [Name], [Age], [Cost] FROM [Animal] WHERE [Deleted] = @Deleted_w0 ORDER BY [IDAnimal] OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY;');
622
622
  }
623
623
  );
624
624
  test
@@ -640,7 +640,7 @@ suite
640
640
  // This is the query generated by the MSSQL dialect
641
641
  _Fable.log.trace('Select Query', tmpQuery.query);
642
642
  Expect(tmpQuery.query.body)
643
- .to.equal('SELECT DISTINCT [Name], [Age], [Cost] FROM [Animal] WHERE [Deleted] = @Deleted_w0 OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY;');
643
+ .to.equal('SELECT DISTINCT [Name], [Age], [Cost] FROM [Animal] WHERE [Deleted] = @Deleted_w0 ORDER BY [IDAnimal] OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY;');
644
644
  }
645
645
  );
646
646
  test
@@ -669,7 +669,7 @@ suite
669
669
  // This is the query generated by the MSSQL dialect
670
670
  _Fable.log.trace('Select Query', tmpQuery.query);
671
671
  Expect(tmpQuery.query.body)
672
- .to.equal('SELECT [Name], [Age], [Cost] FROM [Animal] WHERE [Age] = @Age_w0 AND ( [Color] = @Color_w2 OR [Color] = @Color_w3 ) AND [Description] IS NOT NULL AND [IDOffice] IN ( @IDOffice_w6 ) AND [Deleted] = @Deleted_w7 OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY;');
672
+ .to.equal('SELECT [Name], [Age], [Cost] FROM [Animal] WHERE [Age] = @Age_w0 AND ( [Color] = @Color_w2 OR [Color] = @Color_w3 ) AND [Description] IS NOT NULL AND [IDOffice] IN ( @IDOffice_w6 ) AND [Deleted] = @Deleted_w7 ORDER BY [IDAnimal] OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY;');
673
673
  }
674
674
  );
675
675
  test
@@ -691,7 +691,62 @@ suite
691
691
  // This is the query generated by the MSSQL dialect
692
692
  _Fable.log.trace('Select Query', tmpQuery.query);
693
693
  Expect(tmpQuery.query.body)
694
- .to.equal('SELECT [Name], [Age], [Cost] FROM [Animal] OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY;');
694
+ .to.equal('SELECT [Name], [Age], [Cost] FROM [Animal] ORDER BY [IDAnimal] OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY;');
695
+ }
696
+ );
697
+ test
698
+ (
699
+ 'Read Query with legacyPagination uses ROW_NUMBER() wrapper (caller sort)',
700
+ function()
701
+ {
702
+ var tmpQuery = libFoxHound.new(_Fable)
703
+ .setDialect('MSSQL')
704
+ .setScope('Animal')
705
+ .setDataElements(['Name', 'Age', 'Cost'])
706
+ .setCap(10)
707
+ .setBegin(20)
708
+ .setSort([{Column:'Age',Direction:'Ascending'}]);
709
+ tmpQuery.query.schema = _AnimalSchema;
710
+ tmpQuery.parameters.legacyPagination = true;
711
+ tmpQuery.buildReadQuery();
712
+ _Fable.log.trace('Legacy Select Query', tmpQuery.query);
713
+ Expect(tmpQuery.query.body)
714
+ .to.equal('SELECT [Name], [Age], [Cost] FROM (SELECT [Name], [Age], [Cost], ROW_NUMBER() OVER (ORDER BY [Age]) AS [_RowNum] FROM [Animal] WHERE [Deleted] = @Deleted_w0) AS [_Paged] WHERE [_RowNum] > 20 AND [_RowNum] <= 30;');
715
+ }
716
+ );
717
+ test
718
+ (
719
+ 'Read Query with legacyPagination injects PK ORDER BY when caller omits sort',
720
+ function()
721
+ {
722
+ var tmpQuery = libFoxHound.new(_Fable)
723
+ .setDialect('MSSQL')
724
+ .setScope('Animal')
725
+ .setDataElements(['Name', 'Age', 'Cost'])
726
+ .setCap(10);
727
+ tmpQuery.query.schema = _AnimalSchema;
728
+ tmpQuery.parameters.legacyPagination = true;
729
+ tmpQuery.buildReadQuery();
730
+ _Fable.log.trace('Legacy Select Query', tmpQuery.query);
731
+ Expect(tmpQuery.query.body)
732
+ .to.equal('SELECT [Name], [Age], [Cost] FROM (SELECT [Name], [Age], [Cost], ROW_NUMBER() OVER (ORDER BY [IDAnimal]) AS [_RowNum] FROM [Animal] WHERE [Deleted] = @Deleted_w0) AS [_Paged] WHERE [_RowNum] > 0 AND [_RowNum] <= 10;');
733
+ }
734
+ );
735
+ test
736
+ (
737
+ 'Read Query with legacyPagination is inert without cap',
738
+ function()
739
+ {
740
+ // No cap → no pagination at all (legacy or otherwise);
741
+ // the flag should be a no-op and not produce a ROW_NUMBER wrapper.
742
+ var tmpQuery = libFoxHound.new(_Fable)
743
+ .setDialect('MSSQL')
744
+ .setScope('Animal');
745
+ tmpQuery.addSort({Column:'Cost',Direction:'Descending'});
746
+ tmpQuery.parameters.legacyPagination = true;
747
+ tmpQuery.buildReadQuery();
748
+ Expect(tmpQuery.query.body)
749
+ .to.equal('SELECT [Animal].* FROM [Animal] ORDER BY [Cost] DESC;');
695
750
  }
696
751
  );
697
752
  test