orange-orm 4.10.0-beta.0 → 5.0.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -51,7 +51,7 @@ Watch the [tutorial video on YouTube](https://youtu.be/1IwwjPr2lMs)
51
51
  ![Relations diagram](./docs/diagram.svg)
52
52
 
53
53
  <sub>📄 map.ts</sub>
54
- ```javascript
54
+ ```ts
55
55
  import orange from 'orange-orm';
56
56
 
57
57
  const map = orange.map(x => ({
@@ -88,7 +88,7 @@ const map = orange.map(x => ({
88
88
  street: column('street').string(),
89
89
  postalCode: column('postalCode').string(),
90
90
  postalPlace: column('postalPlace').string(),
91
- countryCode: column('countryCode').string(),
91
+ countryCode: column('countryCode').string().enum(['NO', 'SE', 'DK', 'FI', 'IS', 'DE', 'FR', 'NL', 'ES', 'IT']),
92
92
  }))
93
93
 
94
94
  })).map(x => ({
@@ -106,7 +106,7 @@ export default map;
106
106
  ```
107
107
  <sub>📄 update.ts</sub>
108
108
 
109
- ```javascript
109
+ ```ts
110
110
  import map from './map';
111
111
  const db = map.sqlite('demo.db');
112
112
 
@@ -127,7 +127,7 @@ async function updateRow() {
127
127
  ```
128
128
  <sub>📄 filter.ts</sub>
129
129
 
130
- ```javascript
130
+ ```ts
131
131
  import map from './map';
132
132
  const db = map.sqlite('demo.db');
133
133
 
@@ -157,7 +157,8 @@ Each column within your database table is designated by using the <strong><i>col
157
157
  Relationships between tables can also be outlined. By using methods like <strong><i>hasOne</i></strong>, <strong><i>hasMany</i></strong>, and <strong><i>references</i></strong>, you can establish connections that reflect the relationships in your data schema. In the example below, an 'order' is linked to a 'customer' reference, a 'deliveryAddress', and multiple 'lines'. The hasMany and hasOne relations represents ownership - the tables 'deliveryAddress' and 'orderLine' are owned by the 'order' table, and therefore, they contain the 'orderId' column referring to their parent table, which is 'order'. The similar relationship exists between orderLine and package - hence the packages are owned by the orderLine. Conversely, the customer table is independent and can exist without any knowledge of the 'order' table. Therefore we say that the order table <i>references</i> the customer table - necessitating the existence of a 'customerId' column in the 'order' table.</p>
158
158
 
159
159
  <sub>📄 map.ts</sub>
160
- ```javascript
160
+
161
+ ```ts
161
162
  import orange from 'orange-orm';
162
163
 
163
164
  const map = orange.map(x => ({
@@ -193,7 +194,7 @@ const map = orange.map(x => ({
193
194
  street: column('street').string(),
194
195
  postalCode: column('postalCode').string(),
195
196
  postalPlace: column('postalPlace').string(),
196
- countryCode: column('countryCode').string(),
197
+ countryCode: column('countryCode').string().enum(['NO', 'SE', 'DK', 'FI', 'IS', 'DE', 'FR', 'NL', 'ES', 'IT']),
197
198
  }))
198
199
 
199
200
  })).map(x => ({
@@ -218,7 +219,8 @@ Then, we define a SQL string. This string outlines the structure of our SQLite d
218
219
  Because of a peculiarity in SQLite, which only allows one statement execution at a time, we split this SQL string into separate statements. We do this using the split() method, which breaks up the string at every semicolon.
219
220
 
220
221
  <sub>📄 init.ts</sub>
221
- ```javascript
222
+
223
+ ```ts
222
224
  import map from './map';
223
225
  const db = map.sqlite('demo.db');
224
226
 
@@ -337,7 +339,7 @@ await db.transaction(async (db) => {
337
339
  __From the browser__
338
340
  You can securely use Orange from the browser by utilizing the Express plugin, which serves to safeguard sensitive database credentials from exposure at the client level. This technique bypasses the need to transmit raw SQL queries directly from the client to the server. Instead, it logs method calls initiated by the client, which are later replayed and authenticated on the server. This not only reinforces security by preventing the disclosure of raw SQL queries on the client side but also facilitates a smoother operation. Essentially, this method mirrors a traditional REST API, augmented with advanced TypeScript tooling for enhanced functionality. You can read more about it in the section called [In the browser](#user-content-in-the-browser)
339
341
  <sub>📄 server.ts</sub>
340
- ```javascript
342
+ ```ts
341
343
  import map from './map';
342
344
  import { json } from 'body-parser';
343
345
  import express from 'express';
@@ -354,7 +356,7 @@ express().disable('x-powered-by')
354
356
  ```
355
357
 
356
358
  <sub>📄 browser.ts</sub>
357
- ```javascript
359
+ ```ts
358
360
  import map from './map';
359
361
 
360
362
  const db = map.http('http://localhost:3000/orange');
@@ -432,7 +434,7 @@ database_id = "<your-guid-for-the-database>"
432
434
  ```
433
435
 
434
436
  <sub>📄 src/index.ts</sub>
435
- ```javascript
437
+ ```ts
436
438
  import map from './map';
437
439
 
438
440
  export interface Env {
@@ -1173,7 +1175,8 @@ async function deleteRows() {
1173
1175
  Raw sql queries, raw sql filters and transactions are disabled at the http client due to security reasons. If you would like Orange to support other web frameworks, like nestJs, fastify, etc, please let me know.</p>
1174
1176
 
1175
1177
  <sub>📄 server.ts</sub>
1176
- ```javascript
1178
+
1179
+ ```ts
1177
1180
  import map from './map';
1178
1181
  import { json } from 'body-parser';
1179
1182
  import express from 'express';
@@ -1190,7 +1193,7 @@ express().disable('x-powered-by')
1190
1193
  ```
1191
1194
 
1192
1195
  <sub>📄 browser.ts</sub>
1193
- ```javascript
1196
+ ```ts
1194
1197
  import map from './map';
1195
1198
 
1196
1199
  const db = map.http('http://localhost:3000/orange');
@@ -1221,7 +1224,7 @@ One notable side effect compared to the previous example, is that only the order
1221
1224
 
1222
1225
  <sub>📄 server.ts</sub>
1223
1226
 
1224
- ```javascript
1227
+ ```ts
1225
1228
  import map from './map';
1226
1229
  import { json } from 'body-parser';
1227
1230
  import express from 'express';
@@ -1256,7 +1259,7 @@ function validateToken(req, res, next) {
1256
1259
 
1257
1260
  <sub>📄 browser.ts</sub>
1258
1261
 
1259
- ```javascript
1262
+ ```ts
1260
1263
  import map from './map';
1261
1264
 
1262
1265
  const db = map.http('http://localhost:3000/orange');
@@ -1300,7 +1303,7 @@ async function updateRows() {
1300
1303
 
1301
1304
  ```
1302
1305
 
1303
- __Row Level Security (Postgres)__
1306
+ __Row Level Security__
1304
1307
  You can enforce tenant isolation at the database level by combining Postgres RLS with Express hooks. The example below mirrors the “Interceptors and base filter” style by putting the tenant id in a (fake) token on the client, then extracting it on the server and setting it inside the transaction. This is convenient for a demo because we can seed data and prove rows are filtered. In a real application you must validate signatures and derive tenant id from a trusted identity source, not from arbitrary client input.
1305
1308
 
1306
1309
  <sub>📄 setup.sql</sub>
@@ -1328,7 +1331,7 @@ insert into tenant_data (tenant_id, value) values
1328
1331
 
1329
1332
  <sub>📄 server.ts</sub>
1330
1333
 
1331
- ```javascript
1334
+ ```ts
1332
1335
  import map from './map';
1333
1336
  import { json } from 'body-parser';
1334
1337
  import express from 'express';
@@ -1343,6 +1346,7 @@ express().disable('x-powered-by')
1343
1346
  .use('/orange', db.express({
1344
1347
  hooks: {
1345
1348
  transaction: {
1349
+ //beforeBegin: async (db, req) => ...,
1346
1350
  afterBegin: async (db, req) => {
1347
1351
  const tenantId = Number.parseInt(String(req.user?.tenantId ?? ''), 10);
1348
1352
  if (!Number.isFinite(tenantId)) throw new Error('Missing tenant id');
@@ -1351,7 +1355,12 @@ express().disable('x-powered-by')
1351
1355
  sql: 'select set_config(\'app.tenant_id\', ?, true)',
1352
1356
  parameters: [String(tenantId)]
1353
1357
  });
1354
- }
1358
+ },
1359
+ //beforeCommit: async (db, req) => ...,
1360
+ //afterCommit: async (db, req) => ...,
1361
+ // afterRollback: async (db, req, error) => {
1362
+ // console.dir(error);
1363
+ // }
1355
1364
  }
1356
1365
  }
1357
1366
  }))
@@ -1380,7 +1389,7 @@ function decodeFakeJwt(token) {
1380
1389
 
1381
1390
  <sub>📄 browser.ts</sub>
1382
1391
 
1383
- ```javascript
1392
+ ```ts
1384
1393
  import map from './map';
1385
1394
 
1386
1395
  const db = map.http('http://localhost:3000/orange');
@@ -1793,7 +1802,8 @@ async function execute() {
1793
1802
  - **`json`** and **`jsonOf<T>`** are represented as an object or array in javascript and maps to JSON, JSONB, NVARCHAR(max) or TEXT (sqlite) in sql.
1794
1803
 
1795
1804
  <sub>📄 map.ts</sub>
1796
- ```javascript
1805
+
1806
+ ```ts
1797
1807
  import orange from 'orange-orm';
1798
1808
 
1799
1809
  interface Pet {
@@ -1816,7 +1826,8 @@ const map = orange.map(x => ({
1816
1826
  }));
1817
1827
  ```
1818
1828
  <sub>📄 map.js</sub>
1819
- ```javascript
1829
+
1830
+ ```js
1820
1831
  import orange from 'orange-orm';
1821
1832
 
1822
1833
  /**
@@ -1843,6 +1854,138 @@ const map = orange.map(x => ({
1843
1854
  ```
1844
1855
  </details>
1845
1856
 
1857
+ <details id="enums"><summary><strong>Enums</strong></summary>
1858
+ <p>Enums can be defined using object literals, arrays, or TypeScript enums. The <strong><i>enum(...)</i></strong> method uses literal types when possible, so prefer patterns that keep literals (inline objects, <code>as const</code>, or <code>Object.freeze</code> in JS).</p>
1859
+
1860
+ <sub>📄 map.ts (TypeScript enum)</sub>
1861
+
1862
+ ```ts
1863
+ enum CountryCode {
1864
+ NORWAY = 'NO',
1865
+ SWEDEN = 'SE',
1866
+ DENMARK = 'DK',
1867
+ FINLAND = 'FI',
1868
+ ICELAND = 'IS',
1869
+ GERMANY = 'DE',
1870
+ FRANCE = 'FR',
1871
+ NETHERLANDS = 'NL',
1872
+ SPAIN = 'ES',
1873
+ ITALY = 'IT',
1874
+ }
1875
+
1876
+ const map = orange.map(x => ({
1877
+ deliveryAddress: x.table('deliveryAddress').map(({ column }) => ({
1878
+ id: column('id').numeric().primary(),
1879
+ name: column('name').string(),
1880
+ street: column('street').string(),
1881
+ postalCode: column('postalCode').string(),
1882
+ postalPlace: column('postalPlace').string(),
1883
+ countryCode: column('countryCode').string().enum(CountryCode),
1884
+ }))
1885
+ }));
1886
+ ```
1887
+
1888
+
1889
+ <sub>📄 map.ts (as const)</sub>
1890
+
1891
+ ```ts
1892
+ const Countries = {
1893
+ NORWAY: 'NO',
1894
+ SWEDEN: 'SE',
1895
+ DENMARK: 'DK',
1896
+ FINLAND: 'FI',
1897
+ ICELAND: 'IS',
1898
+ GERMANY: 'DE',
1899
+ FRANCE: 'FR',
1900
+ NETHERLANDS: 'NL',
1901
+ SPAIN: 'ES',
1902
+ ITALY: 'IT',
1903
+ } as const;
1904
+
1905
+ const map = orange.map(x => ({
1906
+ deliveryAddress: x.table('deliveryAddress').map(({ column }) => ({
1907
+ id: column('id').numeric().primary(),
1908
+ name: column('name').string(),
1909
+ street: column('street').string(),
1910
+ postalCode: column('postalCode').string(),
1911
+ postalPlace: column('postalPlace').string(),
1912
+ countryCode: column('countryCode').string().enum(Countries),
1913
+ }))
1914
+ }));
1915
+ ```
1916
+
1917
+ <sub>📄 map.ts (array)</sub>
1918
+
1919
+ ```ts
1920
+ const map = orange.map(x => ({
1921
+ deliveryAddress: x.table('deliveryAddress').map(({ column }) => ({
1922
+ id: column('id').numeric().primary(),
1923
+ name: column('name').string(),
1924
+ street: column('street').string(),
1925
+ postalCode: column('postalCode').string(),
1926
+ postalPlace: column('postalPlace').string(),
1927
+ countryCode: column('countryCode').string().enum(
1928
+ ['NO', 'SE', 'DK', 'FI', 'IS', 'DE', 'FR', 'NL', 'ES', 'IT']
1929
+ ),
1930
+ }))
1931
+ }));
1932
+ ```
1933
+
1934
+ <sub>📄 map.ts (literal object)</sub>
1935
+
1936
+ ```ts
1937
+ const map = orange.map(x => ({
1938
+ deliveryAddress: x.table('deliveryAddress').map(({ column }) => ({
1939
+ id: column('id').numeric().primary(),
1940
+ name: column('name').string(),
1941
+ street: column('street').string(),
1942
+ postalCode: column('postalCode').string(),
1943
+ postalPlace: column('postalPlace').string(),
1944
+ countryCode: column('countryCode').string().enum({
1945
+ NORWAY: 'NO',
1946
+ SWEDEN: 'SE',
1947
+ DENMARK: 'DK',
1948
+ FINLAND: 'FI',
1949
+ ICELAND: 'IS',
1950
+ GERMANY: 'DE',
1951
+ FRANCE: 'FR',
1952
+ NETHERLANDS: 'NL',
1953
+ SPAIN: 'ES',
1954
+ ITALY: 'IT',
1955
+ }),
1956
+ }))
1957
+ }));
1958
+ ```
1959
+
1960
+ <sub>📄 map.js (Object.freeze)</sub>
1961
+
1962
+ ```js
1963
+ const Countries = Object.freeze({
1964
+ NORWAY: 'NO',
1965
+ SWEDEN: 'SE',
1966
+ DENMARK: 'DK',
1967
+ FINLAND: 'FI',
1968
+ ICELAND: 'IS',
1969
+ GERMANY: 'DE',
1970
+ FRANCE: 'FR',
1971
+ NETHERLANDS: 'NL',
1972
+ SPAIN: 'ES',
1973
+ ITALY: 'IT',
1974
+ });
1975
+
1976
+ const map = orange.map(x => ({
1977
+ deliveryAddress: x.table('deliveryAddress').map(({ column }) => ({
1978
+ id: column('id').numeric().primary(),
1979
+ name: column('name').string(),
1980
+ street: column('street').string(),
1981
+ postalCode: column('postalCode').string(),
1982
+ postalPlace: column('postalPlace').string(),
1983
+ countryCode: column('countryCode').string().enum(Countries),
1984
+ }))
1985
+ }));
1986
+ ```
1987
+ </details>
1988
+
1846
1989
  <details id="default-values"><summary><strong>Default values</strong></summary>
1847
1990
  <p>Utilizing default values can be especially useful for automatically populating these fields when the underlying database doesn't offer native support for default value generation.
1848
1991
 
@@ -1869,7 +2012,7 @@ export default map;
1869
2012
  <p>In the previous sections you have already seen the <strong><i>notNull()</i></strong> validator being used on some columns. This will not only generate correct typescript mapping, but also throw an error if value is set to null or undefined. However, sometimes we do not want the notNull-validator to be run on inserts. Typically, when we have an autoincremental key or server generated uuid, it does not make sense to check for null on insert. This is where <strong><i>notNullExceptInsert()</strong></i> comes to rescue. You can also create your own custom validator as shown below. The last kind of validator, is the <a href="https://ajv.js.org/json-schema.html">ajv JSON schema validator</a>. This can be used on json columns as well as any other column type.</p>
1870
2013
 
1871
2014
  <sub>📄 map.ts</sub>
1872
- ```javascript
2015
+ ```ts
1873
2016
  import orange from 'orange-orm';
1874
2017
 
1875
2018
  interface Pet {
@@ -1900,7 +2043,7 @@ const map = orange.map(x => ({
1900
2043
  export default map;
1901
2044
  ```
1902
2045
  <sub>📄 map.js</sub>
1903
- ```javascript
2046
+ ```js
1904
2047
  import orange from 'orange-orm';
1905
2048
 
1906
2049
  /**
@@ -2110,7 +2253,7 @@ async function getCount() {
2110
2253
 
2111
2254
  <sub>📄 map.ts</sub>
2112
2255
 
2113
- ```javascript
2256
+ ```ts
2114
2257
  import orange from 'orange-orm';
2115
2258
 
2116
2259
  const map = orange.map(x => ({
@@ -2126,7 +2269,7 @@ export default map;
2126
2269
  ```
2127
2270
  <sub>📄 sensitive.ts</sub>
2128
2271
 
2129
- ```javascript
2272
+ ```ts
2130
2273
  import map from './map';
2131
2274
  const db = map.sqlite('demo.db');
2132
2275
 
@@ -790,8 +790,10 @@ function requireStringify () {
790
790
  }
791
791
 
792
792
  function replacer(key, value) {
793
- // // @ts-ignore
794
- if (value instanceof Date && !isNaN(value))
793
+ // @ts-ignore
794
+ if (typeof value === 'bigint')
795
+ return value.toString();
796
+ else if (value instanceof Date && !isNaN(value))
795
797
  return dateToISOString(value);
796
798
  else
797
799
  return value;
@@ -901,6 +903,8 @@ function requireCreatePatch () {
901
903
  }
902
904
  return copy;
903
905
  }
906
+ else if (typeof object === 'bigint')
907
+ return object.toString();
904
908
  else if (isValidDate(object))
905
909
  return dateToIsoString(object);
906
910
  else if (object === Object(object)) {
@@ -1134,8 +1138,9 @@ function requireUtils () {
1134
1138
  }
1135
1139
  params.push(sql, filter.parameters);
1136
1140
  }
1137
- else if (isObjectFilter(filter, optionalTable))
1141
+ else if (isObjectFilter(filter, optionalTable)) {
1138
1142
  return newObjectFilter(context, filter, optionalTable);
1143
+ }
1139
1144
  else
1140
1145
  params = [filter];
1141
1146
  } else {
@@ -3091,6 +3096,14 @@ function requireClient () {
3091
3096
 
3092
3097
  async function getMany(_, strategy) {
3093
3098
  let metaPromise = getMeta();
3099
+ if (looksLikeFetchStrategy(_) && (strategy === undefined || !looksLikeFetchStrategy(strategy))) {
3100
+ let meta = await metaPromise;
3101
+ if (!isPrimaryKeyObject(meta, _)) {
3102
+ let _strategy = _;
3103
+ _ = strategy;
3104
+ strategy = _strategy;
3105
+ }
3106
+ }
3094
3107
  strategy = extractFetchingStrategy({}, strategy);
3095
3108
  let args = [_, strategy].concat(Array.prototype.slice.call(arguments).slice(2));
3096
3109
  let rows = await getManyCore.apply(null, args);
@@ -3118,13 +3131,69 @@ function requireClient () {
3118
3131
  return adapter.post(body);
3119
3132
  }
3120
3133
 
3134
+ function isRawFilter(value) {
3135
+ return value && typeof value === 'object'
3136
+ && (typeof value.sql === 'string' || typeof value.sql === 'function');
3137
+ }
3138
+
3139
+ function looksLikeFetchStrategy(value) {
3140
+ if (!value || typeof value !== 'object' || Array.isArray(value))
3141
+ return false;
3142
+ if (isRawFilter(value))
3143
+ return false;
3144
+ if (Object.keys(value).length === 0)
3145
+ return true;
3146
+ if ('where' in value || 'orderBy' in value || 'limit' in value || 'offset' in value)
3147
+ return true;
3148
+ for (let key in value) {
3149
+ const v = value[key];
3150
+ if (typeof v === 'boolean')
3151
+ return true;
3152
+ if (v && typeof v === 'object' && !Array.isArray(v))
3153
+ return true;
3154
+ }
3155
+ return false;
3156
+ }
3157
+
3158
+ function isPrimaryKeyObject(meta, value) {
3159
+ if (!value || typeof value !== 'object' || Array.isArray(value))
3160
+ return false;
3161
+ if (isRawFilter(value))
3162
+ return false;
3163
+ const keyNames = meta?.keys?.map(key => key.name);
3164
+ if (!keyNames || keyNames.length === 0)
3165
+ return false;
3166
+ const keys = Object.keys(value);
3167
+ if (keys.length === 0)
3168
+ return false;
3169
+ for (let i = 0; i < keys.length; i++) {
3170
+ const key = keys[i];
3171
+ if (!keyNames.includes(key))
3172
+ return false;
3173
+ const val = value[key];
3174
+ if (val && typeof val === 'object' && !(val instanceof Date))
3175
+ return false;
3176
+ }
3177
+ return true;
3178
+ }
3179
+
3180
+ function normalizeGetOneArgs(meta, filter, strategy) {
3181
+ if (looksLikeFetchStrategy(filter) && (strategy === undefined || !looksLikeFetchStrategy(strategy))) {
3182
+ if (!isPrimaryKeyObject(meta, filter))
3183
+ return { filter: strategy, strategy: filter };
3184
+ }
3185
+ return { filter, strategy };
3186
+ }
3187
+
3121
3188
  async function getOne(filter, strategy) {
3122
3189
  let metaPromise = getMeta();
3123
- strategy = extractFetchingStrategy({}, strategy);
3190
+ let meta = await metaPromise;
3191
+ let normalized = normalizeGetOneArgs(meta, filter, strategy);
3192
+ filter = normalized.filter;
3193
+ strategy = extractFetchingStrategy({}, normalized.strategy);
3124
3194
  let _strategy = { ...strategy, ...{ limit: 1 } };
3125
3195
  let args = [filter, _strategy].concat(Array.prototype.slice.call(arguments).slice(2));
3126
3196
  let rows = await getManyCore.apply(null, args);
3127
- await metaPromise;
3128
3197
  if (rows.length === 0)
3129
3198
  return;
3130
3199
  return proxify(rows[0], strategy, true);
@@ -5817,6 +5886,37 @@ function requireColumn () {
5817
5886
  return c;
5818
5887
  };
5819
5888
 
5889
+ c.enum = function(values) {
5890
+ let list = values;
5891
+ if (Array.isArray(values))
5892
+ list = values;
5893
+ else if (values && typeof values === 'object') {
5894
+ const keys = Object.keys(values);
5895
+ const nonNumericKeys = keys.filter((key) => !/^-?\d+$/.test(key));
5896
+ list = (nonNumericKeys.length ? nonNumericKeys : keys).map((key) => values[key]);
5897
+ }
5898
+ else
5899
+ throw new Error('enum values must be an array');
5900
+ const allowed = new Set(list);
5901
+ column.enum = list;
5902
+ function validate(value) {
5903
+ if (value === undefined || value === null)
5904
+ return;
5905
+ if (!allowed.has(value)) {
5906
+ const formatted = list.map((v) => JSON.stringify(v)).join(', ');
5907
+ throw new Error(`Column ${column.alias} must be one of: ${formatted}`);
5908
+ }
5909
+ }
5910
+ return c.validate(validate);
5911
+ };
5912
+
5913
+ c.enum2 = function(...values) {
5914
+ const list = values.length === 1 && Array.isArray(values[0])
5915
+ ? values[0]
5916
+ : values;
5917
+ return c.enum(list);
5918
+ };
5919
+
5820
5920
  c.default = function(value) {
5821
5921
  column.default = value;
5822
5922
  return c;
@@ -5832,7 +5932,6 @@ function requireColumn () {
5832
5932
  var oldAlias = column.alias;
5833
5933
  table._aliases.delete(oldAlias);
5834
5934
  table._aliases.add(alias);
5835
- delete table[oldAlias];
5836
5935
  table[alias] = column;
5837
5936
  column.alias = alias;
5838
5937
  return c;
@@ -9682,7 +9781,7 @@ function requireWhere$1 () {
9682
9781
 
9683
9782
  try {
9684
9783
  let arg = typeof fn === 'function' ? fn(table) : fn;
9685
- let anyFilter = negotiateRawSqlFilter(context, arg);
9784
+ let anyFilter = negotiateRawSqlFilter(context, arg, table, true);
9686
9785
  delete table._rootAlias;
9687
9786
  return anyFilter;
9688
9787
  }
@@ -12339,7 +12438,7 @@ function requireWhere () {
12339
12438
 
12340
12439
  function where(context, fn) {
12341
12440
  let arg = typeof fn === 'function' ? fn(table) : fn;
12342
- return negotiateRawSqlFilter(context, arg);
12441
+ return negotiateRawSqlFilter(context, arg, table, true);
12343
12442
  }
12344
12443
  return where;
12345
12444
  }
package/dist/index.mjs CHANGED
@@ -791,8 +791,10 @@ function requireStringify () {
791
791
  }
792
792
 
793
793
  function replacer(key, value) {
794
- // // @ts-ignore
795
- if (value instanceof Date && !isNaN(value))
794
+ // @ts-ignore
795
+ if (typeof value === 'bigint')
796
+ return value.toString();
797
+ else if (value instanceof Date && !isNaN(value))
796
798
  return dateToISOString(value);
797
799
  else
798
800
  return value;
@@ -902,6 +904,8 @@ function requireCreatePatch () {
902
904
  }
903
905
  return copy;
904
906
  }
907
+ else if (typeof object === 'bigint')
908
+ return object.toString();
905
909
  else if (isValidDate(object))
906
910
  return dateToIsoString(object);
907
911
  else if (object === Object(object)) {
@@ -1135,8 +1139,9 @@ function requireUtils () {
1135
1139
  }
1136
1140
  params.push(sql, filter.parameters);
1137
1141
  }
1138
- else if (isObjectFilter(filter, optionalTable))
1142
+ else if (isObjectFilter(filter, optionalTable)) {
1139
1143
  return newObjectFilter(context, filter, optionalTable);
1144
+ }
1140
1145
  else
1141
1146
  params = [filter];
1142
1147
  } else {
@@ -3092,6 +3097,14 @@ function requireClient () {
3092
3097
 
3093
3098
  async function getMany(_, strategy) {
3094
3099
  let metaPromise = getMeta();
3100
+ if (looksLikeFetchStrategy(_) && (strategy === undefined || !looksLikeFetchStrategy(strategy))) {
3101
+ let meta = await metaPromise;
3102
+ if (!isPrimaryKeyObject(meta, _)) {
3103
+ let _strategy = _;
3104
+ _ = strategy;
3105
+ strategy = _strategy;
3106
+ }
3107
+ }
3095
3108
  strategy = extractFetchingStrategy({}, strategy);
3096
3109
  let args = [_, strategy].concat(Array.prototype.slice.call(arguments).slice(2));
3097
3110
  let rows = await getManyCore.apply(null, args);
@@ -3119,13 +3132,69 @@ function requireClient () {
3119
3132
  return adapter.post(body);
3120
3133
  }
3121
3134
 
3135
+ function isRawFilter(value) {
3136
+ return value && typeof value === 'object'
3137
+ && (typeof value.sql === 'string' || typeof value.sql === 'function');
3138
+ }
3139
+
3140
+ function looksLikeFetchStrategy(value) {
3141
+ if (!value || typeof value !== 'object' || Array.isArray(value))
3142
+ return false;
3143
+ if (isRawFilter(value))
3144
+ return false;
3145
+ if (Object.keys(value).length === 0)
3146
+ return true;
3147
+ if ('where' in value || 'orderBy' in value || 'limit' in value || 'offset' in value)
3148
+ return true;
3149
+ for (let key in value) {
3150
+ const v = value[key];
3151
+ if (typeof v === 'boolean')
3152
+ return true;
3153
+ if (v && typeof v === 'object' && !Array.isArray(v))
3154
+ return true;
3155
+ }
3156
+ return false;
3157
+ }
3158
+
3159
+ function isPrimaryKeyObject(meta, value) {
3160
+ if (!value || typeof value !== 'object' || Array.isArray(value))
3161
+ return false;
3162
+ if (isRawFilter(value))
3163
+ return false;
3164
+ const keyNames = meta?.keys?.map(key => key.name);
3165
+ if (!keyNames || keyNames.length === 0)
3166
+ return false;
3167
+ const keys = Object.keys(value);
3168
+ if (keys.length === 0)
3169
+ return false;
3170
+ for (let i = 0; i < keys.length; i++) {
3171
+ const key = keys[i];
3172
+ if (!keyNames.includes(key))
3173
+ return false;
3174
+ const val = value[key];
3175
+ if (val && typeof val === 'object' && !(val instanceof Date))
3176
+ return false;
3177
+ }
3178
+ return true;
3179
+ }
3180
+
3181
+ function normalizeGetOneArgs(meta, filter, strategy) {
3182
+ if (looksLikeFetchStrategy(filter) && (strategy === undefined || !looksLikeFetchStrategy(strategy))) {
3183
+ if (!isPrimaryKeyObject(meta, filter))
3184
+ return { filter: strategy, strategy: filter };
3185
+ }
3186
+ return { filter, strategy };
3187
+ }
3188
+
3122
3189
  async function getOne(filter, strategy) {
3123
3190
  let metaPromise = getMeta();
3124
- strategy = extractFetchingStrategy({}, strategy);
3191
+ let meta = await metaPromise;
3192
+ let normalized = normalizeGetOneArgs(meta, filter, strategy);
3193
+ filter = normalized.filter;
3194
+ strategy = extractFetchingStrategy({}, normalized.strategy);
3125
3195
  let _strategy = { ...strategy, ...{ limit: 1 } };
3126
3196
  let args = [filter, _strategy].concat(Array.prototype.slice.call(arguments).slice(2));
3127
3197
  let rows = await getManyCore.apply(null, args);
3128
- await metaPromise;
3129
3198
  if (rows.length === 0)
3130
3199
  return;
3131
3200
  return proxify(rows[0], strategy, true);
@@ -5818,6 +5887,37 @@ function requireColumn () {
5818
5887
  return c;
5819
5888
  };
5820
5889
 
5890
+ c.enum = function(values) {
5891
+ let list = values;
5892
+ if (Array.isArray(values))
5893
+ list = values;
5894
+ else if (values && typeof values === 'object') {
5895
+ const keys = Object.keys(values);
5896
+ const nonNumericKeys = keys.filter((key) => !/^-?\d+$/.test(key));
5897
+ list = (nonNumericKeys.length ? nonNumericKeys : keys).map((key) => values[key]);
5898
+ }
5899
+ else
5900
+ throw new Error('enum values must be an array');
5901
+ const allowed = new Set(list);
5902
+ column.enum = list;
5903
+ function validate(value) {
5904
+ if (value === undefined || value === null)
5905
+ return;
5906
+ if (!allowed.has(value)) {
5907
+ const formatted = list.map((v) => JSON.stringify(v)).join(', ');
5908
+ throw new Error(`Column ${column.alias} must be one of: ${formatted}`);
5909
+ }
5910
+ }
5911
+ return c.validate(validate);
5912
+ };
5913
+
5914
+ c.enum2 = function(...values) {
5915
+ const list = values.length === 1 && Array.isArray(values[0])
5916
+ ? values[0]
5917
+ : values;
5918
+ return c.enum(list);
5919
+ };
5920
+
5821
5921
  c.default = function(value) {
5822
5922
  column.default = value;
5823
5923
  return c;
@@ -5833,7 +5933,6 @@ function requireColumn () {
5833
5933
  var oldAlias = column.alias;
5834
5934
  table._aliases.delete(oldAlias);
5835
5935
  table._aliases.add(alias);
5836
- delete table[oldAlias];
5837
5936
  table[alias] = column;
5838
5937
  column.alias = alias;
5839
5938
  return c;
@@ -9683,7 +9782,7 @@ function requireWhere$1 () {
9683
9782
 
9684
9783
  try {
9685
9784
  let arg = typeof fn === 'function' ? fn(table) : fn;
9686
- let anyFilter = negotiateRawSqlFilter(context, arg);
9785
+ let anyFilter = negotiateRawSqlFilter(context, arg, table, true);
9687
9786
  delete table._rootAlias;
9688
9787
  return anyFilter;
9689
9788
  }
@@ -12340,7 +12439,7 @@ function requireWhere () {
12340
12439
 
12341
12440
  function where(context, fn) {
12342
12441
  let arg = typeof fn === 'function' ? fn(table) : fn;
12343
- return negotiateRawSqlFilter(context, arg);
12442
+ return negotiateRawSqlFilter(context, arg, table, true);
12344
12443
  }
12345
12444
  return where;
12346
12445
  }
@@ -17753,7 +17852,6 @@ function requireNewDatabase$7 () {
17753
17852
  };
17754
17853
 
17755
17854
  c.createTransaction = function(options) {
17756
- console.dir('create transaction');
17757
17855
  let domain = createDomain();
17758
17856
  let transaction = newTransaction(domain, pool);
17759
17857
  let p = domain.run(() => new Promise(transaction).then(begin));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "orange-orm",
3
- "version": "4.10.0-beta.0",
3
+ "version": "5.0.0-beta.0",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -90,6 +90,8 @@ module.exports = function createPatch(original, dto, options) {
90
90
  }
91
91
  return copy;
92
92
  }
93
+ else if (typeof object === 'bigint')
94
+ return object.toString();
93
95
  else if (isValidDate(object))
94
96
  return dateToIsoString(object);
95
97
  else if (object === Object(object)) {
@@ -242,6 +242,14 @@ function rdbClient(options = {}) {
242
242
 
243
243
  async function getMany(_, strategy) {
244
244
  let metaPromise = getMeta();
245
+ if (looksLikeFetchStrategy(_) && (strategy === undefined || !looksLikeFetchStrategy(strategy))) {
246
+ let meta = await metaPromise;
247
+ if (!isPrimaryKeyObject(meta, _)) {
248
+ let _strategy = _;
249
+ _ = strategy;
250
+ strategy = _strategy;
251
+ }
252
+ }
245
253
  strategy = extractFetchingStrategy({}, strategy);
246
254
  let args = [_, strategy].concat(Array.prototype.slice.call(arguments).slice(2));
247
255
  let rows = await getManyCore.apply(null, args);
@@ -269,13 +277,69 @@ function rdbClient(options = {}) {
269
277
  return adapter.post(body);
270
278
  }
271
279
 
280
+ function isRawFilter(value) {
281
+ return value && typeof value === 'object'
282
+ && (typeof value.sql === 'string' || typeof value.sql === 'function');
283
+ }
284
+
285
+ function looksLikeFetchStrategy(value) {
286
+ if (!value || typeof value !== 'object' || Array.isArray(value))
287
+ return false;
288
+ if (isRawFilter(value))
289
+ return false;
290
+ if (Object.keys(value).length === 0)
291
+ return true;
292
+ if ('where' in value || 'orderBy' in value || 'limit' in value || 'offset' in value)
293
+ return true;
294
+ for (let key in value) {
295
+ const v = value[key];
296
+ if (typeof v === 'boolean')
297
+ return true;
298
+ if (v && typeof v === 'object' && !Array.isArray(v))
299
+ return true;
300
+ }
301
+ return false;
302
+ }
303
+
304
+ function isPrimaryKeyObject(meta, value) {
305
+ if (!value || typeof value !== 'object' || Array.isArray(value))
306
+ return false;
307
+ if (isRawFilter(value))
308
+ return false;
309
+ const keyNames = meta?.keys?.map(key => key.name);
310
+ if (!keyNames || keyNames.length === 0)
311
+ return false;
312
+ const keys = Object.keys(value);
313
+ if (keys.length === 0)
314
+ return false;
315
+ for (let i = 0; i < keys.length; i++) {
316
+ const key = keys[i];
317
+ if (!keyNames.includes(key))
318
+ return false;
319
+ const val = value[key];
320
+ if (val && typeof val === 'object' && !(val instanceof Date))
321
+ return false;
322
+ }
323
+ return true;
324
+ }
325
+
326
+ function normalizeGetOneArgs(meta, filter, strategy) {
327
+ if (looksLikeFetchStrategy(filter) && (strategy === undefined || !looksLikeFetchStrategy(strategy))) {
328
+ if (!isPrimaryKeyObject(meta, filter))
329
+ return { filter: strategy, strategy: filter };
330
+ }
331
+ return { filter, strategy };
332
+ }
333
+
272
334
  async function getOne(filter, strategy) {
273
335
  let metaPromise = getMeta();
274
- strategy = extractFetchingStrategy({}, strategy);
336
+ let meta = await metaPromise;
337
+ let normalized = normalizeGetOneArgs(meta, filter, strategy);
338
+ filter = normalized.filter;
339
+ strategy = extractFetchingStrategy({}, normalized.strategy);
275
340
  let _strategy = { ...strategy, ...{ limit: 1 } };
276
341
  let args = [filter, _strategy].concat(Array.prototype.slice.call(arguments).slice(2));
277
342
  let rows = await getManyCore.apply(null, args);
278
- await metaPromise;
279
343
  if (rows.length === 0)
280
344
  return;
281
345
  return proxify(rows[0], strategy, true);
@@ -5,11 +5,13 @@ function stringify(value) {
5
5
  }
6
6
 
7
7
  function replacer(key, value) {
8
- // // @ts-ignore
9
- if (value instanceof Date && !isNaN(value))
8
+ // @ts-ignore
9
+ if (typeof value === 'bigint')
10
+ return value.toString();
11
+ else if (value instanceof Date && !isNaN(value))
10
12
  return dateToISOString(value);
11
13
  else
12
14
  return value;
13
15
  }
14
16
 
15
- module.exports = stringify;
17
+ module.exports = stringify;
package/src/map.d.ts CHANGED
@@ -96,7 +96,9 @@ type ReturnArrayOrObj<W, V1, V2> =
96
96
  V1;
97
97
 
98
98
 
99
- type ColumnToType<T> = T extends UuidColumnSymbol
99
+ type ColumnToType<T> = T extends EnumOf<infer E>
100
+ ? E
101
+ : T extends UuidColumnSymbol
100
102
  ? string
101
103
  : T extends StringColumnSymbol
102
104
  ? string
@@ -625,6 +627,10 @@ type NotNullExceptInsert = {
625
627
  [' notNullExceptInsert']: boolean;
626
628
  };
627
629
 
630
+ type EnumOf<T> = {
631
+ [' enum']: T;
632
+ };
633
+
628
634
  type JsonOf<T> = {
629
635
  [' isjsonOf']: boolean;
630
636
  type: T;
@@ -734,6 +740,11 @@ type DateWithTimeZoneValidator<M> = M extends NotNull
734
740
  };
735
741
 
736
742
  type StringColumnTypeDef<M> = StringValidator<M> & {
743
+ enum<const V extends readonly string[]>(values: V): StringColumnTypeDef<M & EnumOf<V[number]>> & EnumOf<V[number]>;
744
+ enum<const V extends Record<string, string>>(values: V): StringColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
745
+ enum<TEnum>(values: Record<string, TEnum>): StringColumnTypeDef<M & EnumOf<TEnum>> & EnumOf<TEnum>;
746
+ enum<E extends string>(values: readonly E[]): StringColumnTypeDef<M & EnumOf<E>> & EnumOf<E>;
747
+ enum<E, V extends Record<string, E>>(values: V): StringColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
737
748
  primary(): StringColumnTypeDef<M & IsPrimary> & IsPrimary;
738
749
  notNull(): StringColumnTypeDef<M & NotNull> & NotNull;
739
750
  notNullExceptInsert(): StringColumnTypeDef<M & NotNull & NotNullExceptInsert> & NotNull & NotNullExceptInsert;
@@ -745,6 +756,10 @@ type StringColumnTypeDef<M> = StringValidator<M> & {
745
756
  M;
746
757
 
747
758
  type NumericColumnTypeDef<M> = NumericValidator<M> & {
759
+ enum<const V extends Record<string, number>>(values: V): NumericColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
760
+ enum<TEnum>(values: Record<string, TEnum>): NumericColumnTypeDef<M & EnumOf<TEnum>> & EnumOf<TEnum>;
761
+ enum<E extends number>(values: readonly E[]): NumericColumnTypeDef<M & EnumOf<E>> & EnumOf<E>;
762
+ enum<E, V extends Record<string, E>>(values: V): NumericColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
748
763
  primary(): NumericColumnTypeDef<M & IsPrimary> & IsPrimary;
749
764
  notNull(): NumericColumnTypeDef<M & NotNull> & NotNull;
750
765
  notNullExceptInsert(): NumericColumnTypeDef<M & NotNull & NotNullExceptInsert> & NotNull & NotNullExceptInsert;
@@ -756,6 +771,10 @@ type NumericColumnTypeDef<M> = NumericValidator<M> & {
756
771
  M;
757
772
 
758
773
  type BigIntColumnTypeDef<M> = BigIntValidator<M> & {
774
+ enum<const V extends Record<string, bigint>>(values: V): BigIntColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
775
+ enum<TEnum>(values: Record<string, TEnum>): BigIntColumnTypeDef<M & EnumOf<TEnum>> & EnumOf<TEnum>;
776
+ enum<E extends bigint>(values: readonly E[]): BigIntColumnTypeDef<M & EnumOf<E>> & EnumOf<E>;
777
+ enum<E, V extends Record<string, E>>(values: V): BigIntColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
759
778
  primary(): BigIntColumnTypeDef<M & IsPrimary> & IsPrimary;
760
779
  notNull(): BigIntColumnTypeDef<M & NotNull> & NotNull;
761
780
  notNullExceptInsert(): BigIntColumnTypeDef<M & NotNull & NotNullExceptInsert> & NotNull & NotNullExceptInsert;
@@ -767,6 +786,10 @@ type BigIntColumnTypeDef<M> = BigIntValidator<M> & {
767
786
  M;
768
787
 
769
788
  type UuidColumnTypeDef<M> = UuidValidator<M> & {
789
+ enum<const V extends Record<string, string>>(values: V): UuidColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
790
+ enum<TEnum>(values: Record<string, TEnum>): UuidColumnTypeDef<M & EnumOf<TEnum>> & EnumOf<TEnum>;
791
+ enum<E extends string>(values: readonly E[]): UuidColumnTypeDef<M & EnumOf<E>> & EnumOf<E>;
792
+ enum<E, V extends Record<string, E>>(values: V): UuidColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
770
793
  primary(): UuidColumnTypeDef<M & IsPrimary> & IsPrimary;
771
794
  notNull(): UuidColumnTypeDef<M & NotNull> & NotNull;
772
795
  notNullExceptInsert(): UuidColumnTypeDef<M & NotNull & NotNullExceptInsert> & NotNull & NotNullExceptInsert;
@@ -789,6 +812,10 @@ type JSONColumnTypeDef<M> = JSONValidator<M> & {
789
812
  M;
790
813
 
791
814
  type BinaryColumnTypeDef<M> = BinaryValidator<M> & {
815
+ enum<const V extends Record<string, string>>(values: V): BinaryColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
816
+ enum<TEnum>(values: Record<string, TEnum>): BinaryColumnTypeDef<M & EnumOf<TEnum>> & EnumOf<TEnum>;
817
+ enum<E extends string>(values: readonly E[]): BinaryColumnTypeDef<M & EnumOf<E>> & EnumOf<E>;
818
+ enum<E, V extends Record<string, E>>(values: V): BinaryColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
792
819
  primary(): BinaryColumnTypeDef<M & IsPrimary> & IsPrimary;
793
820
  notNull(): BinaryColumnTypeDef<M & NotNull> & NotNull;
794
821
  notNullExceptInsert(): BinaryColumnTypeDef<M & NotNull & NotNullExceptInsert> & NotNull & NotNullExceptInsert;
@@ -800,6 +827,10 @@ type BinaryColumnTypeDef<M> = BinaryValidator<M> & {
800
827
  M;
801
828
 
802
829
  type BooleanColumnTypeDef<M> = BooleanValidator<M> & {
830
+ enum<const V extends Record<string, boolean>>(values: V): BooleanColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
831
+ enum<TEnum>(values: Record<string, TEnum>): BooleanColumnTypeDef<M & EnumOf<TEnum>> & EnumOf<TEnum>;
832
+ enum<E extends boolean>(values: readonly E[]): BooleanColumnTypeDef<M & EnumOf<E>> & EnumOf<E>;
833
+ enum<E, V extends Record<string, E>>(values: V): BooleanColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
803
834
  primary(): BooleanColumnTypeDef<M & IsPrimary> & IsPrimary;
804
835
  notNull(): BooleanColumnTypeDef<M & NotNull> & NotNull;
805
836
  notNullExceptInsert(): BooleanColumnTypeDef<M & NotNull & NotNullExceptInsert> & NotNull & NotNullExceptInsert;
@@ -811,6 +842,10 @@ type BooleanColumnTypeDef<M> = BooleanValidator<M> & {
811
842
  M;
812
843
 
813
844
  type DateColumnTypeDef<M> = DateValidator<M> & {
845
+ enum<const V extends Record<string, string | Date>>(values: V): DateColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
846
+ enum<TEnum>(values: Record<string, TEnum>): DateColumnTypeDef<M & EnumOf<TEnum>> & EnumOf<TEnum>;
847
+ enum<E extends string | Date>(values: readonly E[]): DateColumnTypeDef<M & EnumOf<E>> & EnumOf<E>;
848
+ enum<E, V extends Record<string, E>>(values: V): DateColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
814
849
  primary(): DateColumnTypeDef<M & IsPrimary> & IsPrimary;
815
850
  notNull(): DateColumnTypeDef<M & NotNull> & NotNull;
816
851
  notNullExceptInsert(): DateColumnTypeDef<M & NotNull & NotNullExceptInsert> & NotNull & NotNullExceptInsert;
@@ -822,6 +857,10 @@ type DateColumnTypeDef<M> = DateValidator<M> & {
822
857
  M;
823
858
 
824
859
  type DateWithTimeZoneColumnTypeDef<M> = DateValidator<M> & {
860
+ enum<const V extends Record<string, string | Date>>(values: V): DateWithTimeZoneColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
861
+ enum<TEnum>(values: Record<string, TEnum>): DateWithTimeZoneColumnTypeDef<M & EnumOf<TEnum>> & EnumOf<TEnum>;
862
+ enum<E extends string | Date>(values: readonly E[]): DateWithTimeZoneColumnTypeDef<M & EnumOf<E>> & EnumOf<E>;
863
+ enum<E, V extends Record<string, E>>(values: V): DateWithTimeZoneColumnTypeDef<M & EnumOf<V[keyof V]>> & EnumOf<V[keyof V]>;
825
864
  primary(): DateWithTimeZoneColumnTypeDef<M & IsPrimary> & IsPrimary;
826
865
  notNull(): DateWithTimeZoneColumnTypeDef<M & NotNull> & NotNull;
827
866
  notNullExceptInsert(): DateWithTimeZoneColumnTypeDef<M & NotNull & NotNullExceptInsert> & NotNull & NotNullExceptInsert;
@@ -1000,7 +1039,9 @@ type ExtractPrimaryKeyNames<T> =
1000
1039
  type RelationTarget<T> =
1001
1040
  T extends { __tableAlias: infer S } ? Extract<S, string> : string;
1002
1041
 
1003
- type ColumnToSchemaType<T> =
1042
+ type EnumSchema<T> = T extends EnumOf<infer E> ? { ' enum': readonly E[] } : {};
1043
+
1044
+ type ColumnToSchemaType<T> = (
1004
1045
  T extends JsonOf<infer U>
1005
1046
  ? { ' type': 'json'; ' tsType': U }
1006
1047
  & (T extends NotNullExceptInsert ? { ' notNull': true; ' notNullExceptInsert': true }
@@ -1046,7 +1087,8 @@ type ColumnToSchemaType<T> =
1046
1087
  & (T extends NotNullExceptInsert ? { ' notNull': true; ' notNullExceptInsert': true }
1047
1088
  : T extends NotNull ? { ' notNull': true }
1048
1089
  : {}) :
1049
- never;
1090
+ never
1091
+ ) & EnumSchema<T>;
1050
1092
 
1051
1093
  export type MappedDbDef<T> = {
1052
1094
  map<V extends AllowedDbMap<V>>(
@@ -1059,4 +1101,4 @@ export type MappedDbDef<T> = {
1059
1101
  * Usage: type Schema = ReturnType<typeof db.toSchema>
1060
1102
  */
1061
1103
  toSchema: <U = T>() => SchemaFromMappedDb<U>;
1062
- } & T & DbConnectable<T>;
1104
+ } & T & DbConnectable<T>;
package/src/map2.d.ts CHANGED
@@ -10,6 +10,7 @@ export type ORMColumnType = 'string' | 'bigint' | 'uuid' | 'date' | 'numeric' |
10
10
  // Base column definition with space-prefixed required properties
11
11
  export type ORMColumnDefinition = {
12
12
  ' type': ORMColumnType;
13
+ ' enum'?: readonly (string | number | boolean | Date)[] | Record<string, string | number | boolean | Date>;
13
14
  ' notNull'?: boolean;
14
15
  ' notNullExceptInsert'?: boolean;
15
16
  };
@@ -24,7 +25,7 @@ export type ORMJsonColumnDefinition<T = any> = {
24
25
 
25
26
  type NormalizeColumn<T> =
26
27
  T extends ORMColumnType
27
- ? { ' type': T; ' notNull'?: boolean; ' notNullExceptInsert'?: boolean }
28
+ ? { ' type': T; ' enum'?: readonly (string | number | boolean | Date)[] | Record<string, string | number | boolean | Date>; ' notNull'?: boolean; ' notNullExceptInsert'?: boolean }
28
29
  : T extends { ' type': ORMColumnType }
29
30
  ? { ' notNull'?: boolean; ' notNullExceptInsert'?: boolean } & T
30
31
  : T extends { ' type': 'json'; ' tsType': any }
@@ -41,6 +42,8 @@ type IsRequiredInsert<CT> =
41
42
  : false; // Otherwise, it's optional
42
43
 
43
44
  type ColumnTypeToTS<CT> =
45
+ NormalizeColumn<CT> extends { ' enum': readonly (infer E)[] } ? E :
46
+ NormalizeColumn<CT> extends { ' enum': Record<string, infer E> } ? E :
44
47
  NormalizeColumn<CT>[' type'] extends 'numeric' ? number :
45
48
  NormalizeColumn<CT>[' type'] extends 'boolean' ? boolean :
46
49
  NormalizeColumn<CT>[' type'] extends 'json'
@@ -674,41 +677,33 @@ type AggregateCustomSelectorProperties<M extends Record<string, TableDefinition<
674
677
 
675
678
  export type TableClient<M extends Record<string, TableDefinition<M>>, K extends keyof M> = {
676
679
  // Array methods - return arrays with array-level active record methods, but individual items are plain
677
- getAll(): Promise<WithArrayActiveRecord<Array<DeepExpand<Selection<M, K, {}>>>, M, K>>;
678
- getAll<strategy extends FetchStrategy<M, K>>(strategy: strategy): Promise<WithArrayActiveRecord<Array<DeepExpand<Selection<M, K, strategy>>>, M, K>>;
679
- getMany(filter?: RawFilter | Array<PrimaryKeyObject<M, K>>): Promise<WithArrayActiveRecord<Array<DeepExpand<Selection<M, K, {}>>>, M, K>>;
680
- getMany<strategy extends FetchStrategy<M, K>>(filter?: RawFilter | Array<PrimaryKeyObject<M, K>>, strategy?: strategy): Promise<WithArrayActiveRecord<Array<DeepExpand<Selection<M, K, strategy>>>, M, K>>;
680
+ getMany<strategy extends FetchStrategy<M, K>>(strategy?: strategy): Promise<WithArrayActiveRecord<Array<DeepExpand<Selection<M, K, strategy>>>, M, K>>;
681
681
 
682
682
  // Aggregate methods - return plain objects (no active record methods)
683
683
  aggregate<strategy extends AggregateStrategy<M, K>>(strategy: strategy): Promise<Array<DeepExpand<AggregateCustomSelectorProperties<M, K, strategy>>>>;
684
684
 
685
685
  // Single item methods - return individual objects with individual active record methods
686
- getOne<strategy extends FetchStrategy<M, K>>(
687
- filter?: RawFilter | Array<PrimaryKeyObject<M, K>>
688
- ): Promise<WithActiveRecord<DeepExpand<Selection<M, K, strategy>>, M, K>>;
689
-
690
- getOne<strategy extends FetchStrategy<M, K>>(
691
- filter?: RawFilter | Array<PrimaryKeyObject<M, K>>,
686
+ getOne<strategy extends FetchStrategy<M, K> = {}>(
692
687
  strategy?: strategy
693
- ): Promise<WithActiveRecord<DeepExpand<Selection<M, K, strategy>>, M, K>>;
688
+ ): Promise<WithActiveRecord<DeepExpand<Selection<M, K, strategy>>, M, K> | undefined>;
694
689
 
695
690
  getById<strategy extends FetchStrategy<M, K>>(
696
691
  ...args: [...PrimaryKeyArgs<M, K>, strategy: strategy]
697
- ): Promise<WithActiveRecord<DeepExpand<Selection<M, K, strategy>>, M, K>>;
692
+ ): Promise<WithActiveRecord<DeepExpand<Selection<M, K, strategy>>, M, K> | undefined>;
698
693
 
699
694
  getById(
700
695
  ...args: [...PrimaryKeyArgs<M, K>]
701
- ): Promise<WithActiveRecord<DeepExpand<Selection<M, K, {}>>, M, K>>;
696
+ ): Promise<WithActiveRecord<DeepExpand<Selection<M, K, {}>>, M, K> | undefined>;
702
697
 
703
698
  // UPDATED: Bulk update methods with relations support
704
699
  update(
705
700
  row: UpdateRowWithRelations<M, K>,
706
- opts: { where: (row: RootTableRefs<M, K>) => RawFilter }
701
+ opts: { where: (row: RootTableRefs<M, K>) => RawFilter | Array<PrimaryKeyObject<M, K>> }
707
702
  ): Promise<void>;
708
703
 
709
704
  update<strategy extends FetchStrategy<M, K>>(
710
705
  row: UpdateRowWithRelations<M, K>,
711
- opts: { where: (row: RootTableRefs<M, K>) => RawFilter },
706
+ opts: { where: (row: RootTableRefs<M, K>) => RawFilter | Array<PrimaryKeyObject<M, K>> },
712
707
  strategy: strategy
713
708
  ): Promise<WithArrayActiveRecord<Array<DeepExpand<Selection<M, K, strategy>>>, M, K>>;
714
709
 
@@ -48,7 +48,6 @@ function newDatabase(connectionString, poolOptions) {
48
48
  };
49
49
 
50
50
  c.createTransaction = function(options) {
51
- console.dir('create transaction');
52
51
  let domain = createDomain();
53
52
  let transaction = newTransaction(domain, pool);
54
53
  let p = domain.run(() => new Promise(transaction).then(begin));
@@ -80,8 +80,9 @@ function negotiateRawSqlFilter(context, filter, optionalTable, emptyArrayMeansFa
80
80
  }
81
81
  params.push(sql, filter.parameters);
82
82
  }
83
- else if (isObjectFilter(filter, optionalTable))
83
+ else if (isObjectFilter(filter, optionalTable)) {
84
84
  return newObjectFilter(context, filter, optionalTable);
85
+ }
85
86
  else
86
87
  params = [filter];
87
88
  } else {
@@ -110,4 +111,4 @@ function newObjectFilter(context, object, table) {
110
111
  }
111
112
 
112
113
 
113
- module.exports = { negotiateRawSqlFilter, newBoolean};
114
+ module.exports = { negotiateRawSqlFilter, newBoolean};
@@ -54,6 +54,37 @@ function defineColumn(column, table) {
54
54
  return c;
55
55
  };
56
56
 
57
+ c.enum = function(values) {
58
+ let list = values;
59
+ if (Array.isArray(values))
60
+ list = values;
61
+ else if (values && typeof values === 'object') {
62
+ const keys = Object.keys(values);
63
+ const nonNumericKeys = keys.filter((key) => !/^-?\d+$/.test(key));
64
+ list = (nonNumericKeys.length ? nonNumericKeys : keys).map((key) => values[key]);
65
+ }
66
+ else
67
+ throw new Error('enum values must be an array');
68
+ const allowed = new Set(list);
69
+ column.enum = list;
70
+ function validate(value) {
71
+ if (value === undefined || value === null)
72
+ return;
73
+ if (!allowed.has(value)) {
74
+ const formatted = list.map((v) => JSON.stringify(v)).join(', ');
75
+ throw new Error(`Column ${column.alias} must be one of: ${formatted}`);
76
+ }
77
+ }
78
+ return c.validate(validate);
79
+ };
80
+
81
+ c.enum2 = function(...values) {
82
+ const list = values.length === 1 && Array.isArray(values[0])
83
+ ? values[0]
84
+ : values;
85
+ return c.enum(list);
86
+ };
87
+
57
88
  c.default = function(value) {
58
89
  column.default = value;
59
90
  return c;
@@ -69,7 +100,6 @@ function defineColumn(column, table) {
69
100
  var oldAlias = column.alias;
70
101
  table._aliases.delete(oldAlias);
71
102
  table._aliases.add(alias);
72
- delete table[oldAlias];
73
103
  table[alias] = column;
74
104
  column.alias = alias;
75
105
  return c;
@@ -160,4 +190,4 @@ function inspect(obj) {
160
190
  }
161
191
 
162
192
 
163
- module.exports = defineColumn;
193
+ module.exports = defineColumn;
@@ -11,7 +11,7 @@ function newWhere(_relations, _depth) {
11
11
 
12
12
  try {
13
13
  let arg = typeof fn === 'function' ? fn(table) : fn;
14
- let anyFilter = negotiateRawSqlFilter(context, arg);
14
+ let anyFilter = negotiateRawSqlFilter(context, arg, table, true);
15
15
  delete table._rootAlias;
16
16
  return anyFilter;
17
17
  }
@@ -40,4 +40,4 @@ function newWhere(_relations, _depth) {
40
40
 
41
41
  }
42
42
 
43
- module.exports = newWhere;
43
+ module.exports = newWhere;
@@ -4,9 +4,9 @@ function newWhere(table) {
4
4
 
5
5
  function where(context, fn) {
6
6
  let arg = typeof fn === 'function' ? fn(table) : fn;
7
- return negotiateRawSqlFilter(context, arg);
7
+ return negotiateRawSqlFilter(context, arg, table, true);
8
8
  }
9
9
  return where;
10
10
  }
11
11
 
12
- module.exports = newWhere;
12
+ module.exports = newWhere;