breadfruit 2.2.1 → 3.1.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
@@ -1,49 +1,258 @@
1
1
  # breadfruit
2
2
 
3
- [![NPM](https://nodei.co/npm/breadfruit.png?compact=true)](https://nodei.co/npm/breadfruit/)
3
+ [![npm version](https://img.shields.io/npm/v/breadfruit.svg)](https://www.npmjs.com/package/breadfruit)
4
+ [![CI](https://github.com/iceddev/breadfruit/actions/workflows/ci.yml/badge.svg)](https://github.com/iceddev/breadfruit/actions/workflows/ci.yml)
5
+ [![codecov](https://codecov.io/gh/iceddev/breadfruit/branch/main/graph/badge.svg)](https://codecov.io/gh/iceddev/breadfruit)
6
+ [![node](https://img.shields.io/node/v/breadfruit.svg)](https://nodejs.org)
7
+ [![npm downloads](https://img.shields.io/npm/dm/breadfruit.svg)](https://www.npmjs.com/package/breadfruit)
8
+ [![license](https://img.shields.io/npm/l/breadfruit.svg)](https://github.com/iceddev/breadfruit/blob/main/LICENSE)
4
9
 
5
- Not really bread. Not really fruit. Just like this package. Some simple helpers on top of knex.
10
+ Not really bread. Not really fruit. Just like this package. Simple CRUD helpers on top of [knex](https://knexjs.org/).
6
11
 
7
12
  ![breadfruit](happy_breadfruit.png)
8
13
 
9
- ## create an instance of breadfruit
14
+ ## Install
15
+
16
+ ```sh
17
+ npm install breadfruit
18
+ ```
19
+
20
+ Requires Node.js `>=22`.
21
+
22
+ ## Usage
23
+
24
+ Breadfruit is an ES module with a default export.
25
+
26
+ ```js
27
+ import breadfruit from 'breadfruit';
10
28
 
11
- ```javascript
12
29
  const config = {
13
- client: 'postgresql',
30
+ client: 'pg',
14
31
  connection: 'postgres://postgres@localhost:5432/someDatabase',
15
- pool: { min: 1, max: 7 }
32
+ pool: { min: 1, max: 7 },
16
33
  };
17
34
 
18
- const bread = require('breadfruit')(config);
35
+ const { browse, read, edit, add, del, raw } = breadfruit(config);
19
36
  ```
20
37
 
21
- ## Browse, Read, Edit, Add, Delete, Raw
38
+ ## API
39
+
40
+ ### `browse(table, fields, filter, options?)`
22
41
 
23
- ```javascript
24
- const {browse, read, edit, add, del, raw} = require('breadfruit')(config);
42
+ Returns an array of rows.
25
43
 
26
- //get an array of users, by table, columns, and a filter
27
- const users = await browse('users', ['username', 'user_id'], {active: true});
44
+ ```js
45
+ const users = await browse('users', ['username', 'user_id'], { active: true });
46
+ ```
28
47
 
48
+ Supported `options`:
49
+ - `limit` (default `1000`)
50
+ - `offset` (default `0`)
51
+ - `orderBy` — column name or array of column names
52
+ - `sortOrder` — `'ASC'` / `'DESC'` (default `'ASC'`), or an array matching `orderBy`
53
+ - `dateField` (default `'created_at'`)
54
+ - `search_start_date` / `search_end_date` — adds a `whereBetween` on `dateField`
55
+ - `dbApi` — override the internal knex instance (useful for transactions)
29
56
 
30
- //get a single user by table, columns, and a filter
31
- const user = await read('users', ['username', 'first_name'], {user_id: 1337});
57
+ ### `read(table, fields, filter, options?)`
32
58
 
59
+ Returns a single row.
33
60
 
34
- //edit a user by table, returned columns, updated values, and a filter
35
- const updatedUser = await edit('users', ['username', 'first_name'], {first_name: 'Howard'}, {user_id: 1337});
61
+ ```js
62
+ const user = await read('users', ['username', 'first_name'], { user_id: 1337 });
63
+ ```
64
+
65
+ ### `add(table, returnFields, data, options?)`
66
+
67
+ Inserts and returns the new row.
68
+
69
+ ```js
70
+ const newUser = await add('users', ['user_id'], {
71
+ first_name: 'Howard',
72
+ username: 'howitzer',
73
+ });
74
+ ```
75
+
76
+ ### `edit(table, returnFields, data, filter, options?)`
77
+
78
+ Updates matching rows and returns the first updated row.
79
+
80
+ ```js
81
+ const updated = await edit(
82
+ 'users',
83
+ ['username', 'first_name'],
84
+ { first_name: 'Howard' },
85
+ { user_id: 1337 },
86
+ );
87
+ ```
36
88
 
89
+ ### `del(table, filter, options?)`
37
90
 
38
- //add a new user by table, returned columns, and user data
39
- const newUser = await add('users', ['user_id'], {first_name: 'Howard', username: 'howitzer'});
91
+ Deletes matching rows and returns the count.
40
92
 
93
+ ```js
94
+ const count = await del('users', { user_id: 1337 });
95
+ ```
41
96
 
42
- //delete a user by table and a filter
43
- const deleteCount = await del('users', {user_id: 1337});
97
+ ### `raw(sql, options?)`
44
98
 
99
+ Runs a raw SQL statement and returns rows.
45
100
 
46
- //perform a raw query
101
+ ```js
47
102
  const rows = await raw('select * from users');
103
+ ```
104
+
105
+ ### `count(table, filter, options?)`
48
106
 
107
+ Returns the count of matching rows as a number.
108
+
109
+ ```js
110
+ const activeUsers = await count('users', { active: true });
49
111
  ```
112
+
113
+ ### `upsert(table, returnFields, data, conflictColumns, options?)`
114
+
115
+ Inserts a row, or updates on conflict. `conflictColumns` can be a string or array.
116
+
117
+ ```js
118
+ const row = await upsert(
119
+ 'users',
120
+ '*',
121
+ { email: 'luis@example.com', name: 'Luis' },
122
+ 'email',
123
+ );
124
+ ```
125
+
126
+ ### `transaction(callback)`
127
+
128
+ Wraps `knex.transaction()`. Pass the `trx` object as `dbApi` in your method calls.
129
+
130
+ ```js
131
+ await transaction(async (trx) => {
132
+ await add('users', ['id'], { name: 'a' }, { dbApi: trx });
133
+ await add('users', ['id'], { name: 'b' }, { dbApi: trx });
134
+ });
135
+ ```
136
+
137
+ ## Advanced
138
+
139
+ ### Passing an existing Knex instance
140
+
141
+ Instead of a config object, you can pass a Knex instance. Useful when you already have a Knex connection in your app and want breadfruit to use it rather than open a second pool.
142
+
143
+ ```js
144
+ import knex from './db.js';
145
+ import breadfruit from 'breadfruit';
146
+
147
+ const bf = breadfruit(knex);
148
+ ```
149
+
150
+ ### Composite filters
151
+
152
+ Filter values accept operators beyond simple equality.
153
+
154
+ | Shape | SQL |
155
+ |---|---|
156
+ | `{ col: value }` | `col = value` |
157
+ | `{ col: [a, b, c] }` | `col IN (a, b, c)` |
158
+ | `{ col: null }` | `col IS NULL` |
159
+ | `{ col: { eq: x } }` | `col = x` |
160
+ | `{ col: { ne: x } }` | `col != x` |
161
+ | `{ col: { gt: x } }` | `col > x` |
162
+ | `{ col: { gte: x } }` | `col >= x` |
163
+ | `{ col: { lt: x } }` | `col < x` |
164
+ | `{ col: { lte: x } }` | `col <= x` |
165
+ | `{ col: { like: 'x%' } }` | `col LIKE 'x%'` |
166
+ | `{ col: { ilike: 'x%' } }` | `col ILIKE 'x%'` |
167
+ | `{ col: { in: [a, b] } }` | `col IN (a, b)` |
168
+ | `{ col: { notIn: [a, b] } }` | `col NOT IN (a, b)` |
169
+ | `{ col: { between: [a, b] } }` | `col BETWEEN a AND b` |
170
+ | `{ col: { notBetween: [a, b] } }` | `col NOT BETWEEN a AND b` |
171
+ | `{ col: { null: true } }` | `col IS NULL` |
172
+ | `{ col: { null: false } }` | `col IS NOT NULL` |
173
+
174
+ Multiple operators on the same column AND together:
175
+
176
+ ```js
177
+ await browse('events', '*', {
178
+ count: { gt: 1, lte: 100 },
179
+ created_at: { gte: '2026-01-01' },
180
+ });
181
+ ```
182
+
183
+ ### `forTable(tableName, options?)` — table-bound helpers
184
+
185
+ Returns an object with the same BREAD methods but bound to a specific table, with optional **soft delete** and **view-for-reads** behavior.
186
+
187
+ ```js
188
+ const users = bf.forTable('users', {
189
+ softDelete: true,
190
+ viewName: 'users_v',
191
+ });
192
+
193
+ await users.browse('*', { active: true }); // reads from users_v
194
+ await users.del({ id: 42 }); // soft-deletes in users
195
+ await users.restore({ id: 42 }); // un-soft-deletes
196
+ const total = await users.count({}); // respects soft delete
197
+ ```
198
+
199
+ #### Soft delete
200
+
201
+ Three options for the `softDelete` config:
202
+
203
+ ```js
204
+ // 1. Boolean shorthand — uses is_deleted column, true/false
205
+ softDelete: true
206
+
207
+ // 2. Full config
208
+ softDelete: {
209
+ column: 'is_deleted',
210
+ value: true, // set on delete
211
+ undeletedValue: false, // the "active" value for filtering
212
+ }
213
+
214
+ // 3. Timestamp style — deleted_at IS NULL means active
215
+ softDelete: {
216
+ column: 'deleted_at',
217
+ value: 'NOW', // special string -> knex.fn.now()
218
+ undeletedValue: null,
219
+ }
220
+ ```
221
+
222
+ The `value` field accepts:
223
+ - a literal (`true`, `false`, `Date`, etc.)
224
+ - the string `'NOW'` — becomes `knex.fn.now()` so the DB generates the timestamp
225
+ - a Knex raw expression like `knex.fn.now()` or `knex.raw('...')`
226
+ - a function — called at delete time (runs in JS, not DB)
227
+
228
+ #### Reads from a view, writes to the table
229
+
230
+ Pass `viewName` to read from a view while writing to the underlying table. Great for denormalized read paths.
231
+
232
+ ```js
233
+ bf.forTable('users', { viewName: 'user_groups_v' });
234
+ ```
235
+
236
+ #### `withDeleted`
237
+
238
+ Bypass the soft-delete filter for admin or audit views:
239
+
240
+ ```js
241
+ const allUsers = await users.browse('*', {}, { withDeleted: true });
242
+ const count = await users.count({}, { withDeleted: true });
243
+ ```
244
+
245
+ ### Transactions with `forTable`
246
+
247
+ Pass `dbApi: trx` through just like the top-level API:
248
+
249
+ ```js
250
+ await bf.transaction(async (trx) => {
251
+ await users.add('*', { email: 'a@b.c' }, { dbApi: trx });
252
+ await users.edit('*', { active: true }, { email: 'a@b.c' }, { dbApi: trx });
253
+ });
254
+ ```
255
+
256
+ ## License
257
+
258
+ ISC
package/index.js CHANGED
@@ -1,89 +1,248 @@
1
- const knexConstructor = require('knex');
2
-
3
- function connect(settings) {
4
- const knex = knexConstructor(settings);
5
- const defaultOptions = {
6
- dateField: 'created_at',
7
- dbApi: knex,
8
- limit: 1000,
9
- offset: 0,
10
- sortOrder: 'ASC',
11
- };
12
-
13
- return {
14
- browse(table, fields, filter, options = {}) {
15
- const dbApi = options.dbApi || defaultOptions.dbApi || knex;
16
- const limit = options.limit || defaultOptions.limit;
17
- const offset = options.offset || defaultOptions.offset;
18
- const dateField = options.dateField || defaultOptions.dateField;
19
- const sortOrder = options.sortOrder || defaultOptions.sortOrder;
20
-
21
- let query = dbApi(table)
22
- .where(filter)
23
- .select(fields)
24
- .limit(limit)
25
- .offset(offset);
26
-
27
- if (options.search_start_date && options.search_end_date) {
28
- query = query
29
- .whereBetween(dateField, [options.search_start_date, options.search_end_date]);
30
- }
1
+ import knexConstructor from 'knex';
31
2
 
32
- if (options.orderBy) {
33
- if(Array.isArray(options.orderBy)) {
34
- options.orderBy.forEach((orderBy, index) => {
35
- if(Array.isArray(sortOrder)) {
36
- return query = query.orderBy(orderBy, sortOrder[index]);
37
- }
38
- query = query.orderBy(orderBy, sortOrder);
39
- });
3
+ const defaults = {
4
+ dateField: 'created_at',
5
+ limit: 1000,
6
+ offset: 0,
7
+ sortOrder: 'ASC',
8
+ };
9
+
10
+ const OPERATORS = {
11
+ eq: '=',
12
+ ne: '!=',
13
+ gt: '>',
14
+ gte: '>=',
15
+ lt: '<',
16
+ lte: '<=',
17
+ like: 'like',
18
+ ilike: 'ilike',
19
+ };
20
+
21
+ // Apply a filter object to a Knex query. Supports:
22
+ // { col: value } -> WHERE col = value
23
+ // { col: [a, b, c] } -> WHERE col IN (...)
24
+ // { col: null } -> WHERE col IS NULL
25
+ // { col: { gt: x, lte: y } } -> WHERE col > x AND col <= y
26
+ // { col: { in: [a, b] } } -> WHERE col IN (...)
27
+ // { col: { notIn: [a, b] } } -> WHERE col NOT IN (...)
28
+ // { col: { between: [a, b] } } -> WHERE col BETWEEN a AND b
29
+ function applyFilter(query, filter) {
30
+ if (!filter) return query;
31
+ for (const [key, value] of Object.entries(filter)) {
32
+ if (value === undefined) continue;
33
+ if (value === null) {
34
+ query = query.whereNull(key);
35
+ } else if (Array.isArray(value)) {
36
+ query = query.whereIn(key, value);
37
+ } else if (typeof value === 'object' && !(value instanceof Date) && !value.toSQL) {
38
+ for (const [op, operand] of Object.entries(value)) {
39
+ if (operand === undefined) continue;
40
+ if (op === 'in') query = query.whereIn(key, operand);
41
+ else if (op === 'notIn') query = query.whereNotIn(key, operand);
42
+ else if (op === 'between') query = query.whereBetween(key, operand);
43
+ else if (op === 'notBetween') query = query.whereNotBetween(key, operand);
44
+ else if (op === 'null') {
45
+ query = operand ? query.whereNull(key) : query.whereNotNull(key);
46
+ } else if (OPERATORS[op]) {
47
+ query = query.where(key, OPERATORS[op], operand);
40
48
  } else {
41
- query = query.orderBy(options.orderBy, sortOrder);
49
+ throw new Error(`Unknown filter operator: ${op}`);
42
50
  }
43
51
  }
52
+ } else {
53
+ query = query.where(key, value);
54
+ }
55
+ }
56
+ return query;
57
+ }
44
58
 
45
- return query;
46
- },
47
- read(table, fields, filter, options = {}) {
48
- const dbApi = options.dbApi || knex;
49
- return dbApi(table)
50
- .where(filter)
51
- .select(fields)
52
- .then(([row]) => {
53
- return row;
54
- });
55
- },
56
- add(table, fields, data, options = {}) {
57
- const dbApi = options.dbApi || knex;
58
- return dbApi(table)
59
- .returning(fields)
60
- .insert(data)
61
- .then(([row]) => {
62
- return row;
63
- });
64
- },
65
- edit(table, fields, data, filter, options = {}) {
66
- const dbApi = options.dbApi || knex;
67
- return dbApi(table)
68
- .where(filter)
69
- .returning(fields)
70
- .update(data)
71
- .then(([row]) => {
72
- return row;
59
+ // Resolve a soft-delete config to { column, value, undeletedValue }.
60
+ // Accepts:
61
+ // true -> { column: 'is_deleted', value: true, undeletedValue: false }
62
+ // { column, value, undeletedValue } -> used as-is
63
+ // value can be:
64
+ // - a literal (true, false, Date, etc.)
65
+ // - the string 'NOW' -> knex.fn.now()
66
+ // - a Knex raw expression (e.g. knex.fn.now())
67
+ // - a function -> called at delete time
68
+ function resolveSoftDelete(config, knex) {
69
+ if (!config) return null;
70
+ if (config === true) {
71
+ return { column: 'is_deleted', value: true, undeletedValue: false };
72
+ }
73
+ if (typeof config !== 'object') {
74
+ throw new Error('softDelete must be true or an object');
75
+ }
76
+ const column = config.column || 'is_deleted';
77
+ const undeletedValue = 'undeletedValue' in config ? config.undeletedValue : false;
78
+ let value = 'value' in config ? config.value : true;
79
+ if (value === 'NOW') value = knex.fn.now();
80
+ return { column, value, undeletedValue };
81
+ }
82
+
83
+ function resolveValue(value) {
84
+ return typeof value === 'function' ? value() : value;
85
+ }
86
+
87
+ export default function connect(settings) {
88
+ // Allow passing an existing knex instance or a config
89
+ const knex = typeof settings === 'function' ? settings : knexConstructor(settings);
90
+
91
+ function browse(table, fields, filter, options = {}) {
92
+ const dbApi = options.dbApi || knex;
93
+ const limit = options.limit ?? defaults.limit;
94
+ const offset = options.offset ?? defaults.offset;
95
+ const dateField = options.dateField || defaults.dateField;
96
+ const sortOrder = options.sortOrder || defaults.sortOrder;
97
+
98
+ let query = dbApi(table).select(fields).limit(limit).offset(offset);
99
+
100
+ query = applyFilter(query, filter);
101
+
102
+ if (options.search_start_date && options.search_end_date) {
103
+ query = query.whereBetween(dateField, [
104
+ options.search_start_date,
105
+ options.search_end_date,
106
+ ]);
107
+ }
108
+
109
+ if (options.orderBy) {
110
+ if (Array.isArray(options.orderBy)) {
111
+ options.orderBy.forEach((orderBy, index) => {
112
+ const order = Array.isArray(sortOrder) ? sortOrder[index] : sortOrder;
113
+ query = query.orderBy(orderBy, order);
73
114
  });
74
- },
75
- del(table, filter, options = {}) {
76
- const dbApi = options.dbApi || knex;
77
- return dbApi(table)
78
- .where(filter)
79
- .del();
80
- },
81
- raw(sql, options = {}) {
82
- const dbApi = options.dbApi || knex;
83
- return dbApi.raw(sql, options)
84
- .then(res => res.rows || res);
115
+ } else {
116
+ query = query.orderBy(options.orderBy, sortOrder);
117
+ }
85
118
  }
86
- };
87
- }
88
119
 
89
- module.exports = connect;
120
+ return query;
121
+ }
122
+
123
+ async function read(table, fields, filter, options = {}) {
124
+ const dbApi = options.dbApi || knex;
125
+ let query = dbApi(table).select(fields);
126
+ query = applyFilter(query, filter);
127
+ const row = await query.first();
128
+ return row || null;
129
+ }
130
+
131
+ async function add(table, fields, data, options = {}) {
132
+ const dbApi = options.dbApi || knex;
133
+ const [row] = await dbApi(table).returning(fields).insert(data);
134
+ return row;
135
+ }
136
+
137
+ async function edit(table, fields, data, filter, options = {}) {
138
+ const dbApi = options.dbApi || knex;
139
+ let query = dbApi(table).returning(fields).update(data);
140
+ query = applyFilter(query, filter);
141
+ const [row] = await query;
142
+ return row;
143
+ }
144
+
145
+ function del(table, filter, options = {}) {
146
+ const dbApi = options.dbApi || knex;
147
+ let query = dbApi(table).del();
148
+ query = applyFilter(query, filter);
149
+ return query;
150
+ }
151
+
152
+ async function count(table, filter, options = {}) {
153
+ const dbApi = options.dbApi || knex;
154
+ let query = dbApi(table).count('* as total');
155
+ query = applyFilter(query, filter);
156
+ const [row] = await query;
157
+ return Number(row.total);
158
+ }
159
+
160
+ async function upsert(table, fields, data, conflictColumns, options = {}) {
161
+ const dbApi = options.dbApi || knex;
162
+ const cols = Array.isArray(conflictColumns) ? conflictColumns : [conflictColumns];
163
+ const [row] = await dbApi(table)
164
+ .insert(data)
165
+ .onConflict(cols)
166
+ .merge()
167
+ .returning(fields);
168
+ return row;
169
+ }
170
+
171
+ async function raw(sql, options = {}) {
172
+ const dbApi = options.dbApi || knex;
173
+ const res = await dbApi.raw(sql, options);
174
+ return res.rows || res;
175
+ }
176
+
177
+ function transaction(callback) {
178
+ return knex.transaction(callback);
179
+ }
180
+
181
+ // Table-bound API with optional softDelete and viewName support.
182
+ // Returns an object with the same BREAD methods but bound to a specific table,
183
+ // with soft-delete filtering baked into browse/read/del, and optional
184
+ // separate readTable (view) for reads vs writes.
185
+ function forTable(tableName, tableOptions = {}) {
186
+ const softDelete = resolveSoftDelete(tableOptions.softDelete, knex);
187
+ const readTable = tableOptions.viewName || tableName;
188
+ const writeTable = tableName;
189
+
190
+ function addSoftDeleteFilter(filter = {}, options = {}) {
191
+ if (!softDelete) return filter;
192
+ if (options.withDeleted) return filter;
193
+ // Don't override if caller explicitly set the column
194
+ if (filter[softDelete.column] !== undefined) return filter;
195
+ return { ...filter, [softDelete.column]: softDelete.undeletedValue };
196
+ }
197
+
198
+ return {
199
+ browse(fields, filter, options = {}) {
200
+ return browse(readTable, fields, addSoftDeleteFilter(filter, options), options);
201
+ },
202
+ read(fields, filter, options = {}) {
203
+ return read(readTable, fields, addSoftDeleteFilter(filter, options), options);
204
+ },
205
+ add(fields, data, options = {}) {
206
+ return add(writeTable, fields, data, options);
207
+ },
208
+ edit(fields, data, filter, options = {}) {
209
+ return edit(writeTable, fields, data, addSoftDeleteFilter(filter, options), options);
210
+ },
211
+ del(filter, options = {}) {
212
+ if (softDelete) {
213
+ const dbApi = options.dbApi || knex;
214
+ let query = dbApi(writeTable).update({
215
+ [softDelete.column]: resolveValue(softDelete.value),
216
+ });
217
+ query = applyFilter(query, addSoftDeleteFilter(filter, options));
218
+ return query;
219
+ }
220
+ return del(writeTable, filter, options);
221
+ },
222
+ restore(filter, options = {}) {
223
+ if (!softDelete) {
224
+ throw new Error('restore() requires softDelete to be configured');
225
+ }
226
+ const dbApi = options.dbApi || knex;
227
+ let query = dbApi(writeTable).update({
228
+ [softDelete.column]: softDelete.undeletedValue,
229
+ });
230
+ // When restoring, look only at deleted rows
231
+ const restoreFilter = { ...filter, [softDelete.column]: { ne: softDelete.undeletedValue } };
232
+ query = applyFilter(query, restoreFilter);
233
+ return query;
234
+ },
235
+ count(filter, options = {}) {
236
+ return count(readTable, addSoftDeleteFilter(filter, options), options);
237
+ },
238
+ upsert(fields, data, conflictColumns, options = {}) {
239
+ return upsert(writeTable, fields, data, conflictColumns, options);
240
+ },
241
+ softDelete,
242
+ tableName: writeTable,
243
+ readTable,
244
+ };
245
+ }
246
+
247
+ return { browse, read, add, edit, del, raw, count, upsert, transaction, forTable, knex };
248
+ }
package/package.json CHANGED
@@ -1,10 +1,28 @@
1
1
  {
2
2
  "name": "breadfruit",
3
- "version": "2.2.1",
4
- "description": "Boilerplate sql query system for Node.js using Knex",
5
- "main": "index.js",
3
+ "version": "3.1.0",
4
+ "description": "Boilerplate SQL query helpers for Node.js using Knex",
5
+ "type": "module",
6
+ "main": "./index.js",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./index.js",
10
+ "default": "./index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "index.js",
15
+ "README.md"
16
+ ],
17
+ "engines": {
18
+ "node": ">=22"
19
+ },
6
20
  "scripts": {
7
- "test": "nyc --reporter=lcov --reporter=text-summary mocha"
21
+ "lint": "eslint .",
22
+ "lint:fix": "eslint . --fix",
23
+ "test": "node --test",
24
+ "test:coverage": "c8 --reporter=text --reporter=lcov node --test",
25
+ "prepublishOnly": "npm run lint && npm test"
8
26
  },
9
27
  "author": "Alyson Zepeda",
10
28
  "license": "ISC",
@@ -14,14 +32,26 @@
14
32
  "homepage": "https://github.com/iceddev/breadfruit#readme",
15
33
  "repository": {
16
34
  "type": "git",
17
- "url": "git+ssh://git@github.com/iceddev/breadfruit.git"
35
+ "url": "git+https://github.com/iceddev/breadfruit.git"
18
36
  },
37
+ "keywords": [
38
+ "knex",
39
+ "sql",
40
+ "crud",
41
+ "postgres",
42
+ "mysql",
43
+ "database"
44
+ ],
19
45
  "dependencies": {
20
- "knex": "^0.21.5"
46
+ "knex": "^3.2.0"
21
47
  },
22
48
  "devDependencies": {
23
- "chai": "^4.2.0",
24
- "mocha": "^8.1.3",
25
- "nyc": "^15.1.0"
49
+ "@eslint/js": "^10.0.1",
50
+ "c8": "^11.0.0",
51
+ "eslint": "^10.2.0",
52
+ "globals": "^17.5.0"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public"
26
56
  }
27
57
  }
package/.coveralls.yml DELETED
@@ -1,2 +0,0 @@
1
- service_name: travis-pro
2
- repo_token:
package/.travis.yml DELETED
@@ -1,4 +0,0 @@
1
- language: node_js
2
- node_js:
3
- - '6'
4
- after_success: 'npm run coveralls'
Binary file
package/test/index.js DELETED
@@ -1,95 +0,0 @@
1
- 'use strict';
2
-
3
- var chai = require('chai');
4
-
5
-
6
- var expect = chai.expect;
7
- chai.should();
8
-
9
- var breadfruit = require('../');
10
-
11
- describe('breadfruit', function(){
12
-
13
- it('be awesome', function(done){
14
- done();
15
- });
16
-
17
- it('should create an instance of the api', function(done) {
18
- const api = breadfruit({client: 'pg'});
19
- api.should.be.an('object');
20
- done();
21
- });
22
-
23
- it('should fail to browse without a real connection', function(done) {
24
- const api = breadfruit({client: 'pg'});
25
- api.browse('tableName', [], {})
26
- .catch((error) => {
27
- done();
28
- });
29
- });
30
-
31
- it('should fail to browse without a real connection', function(done) {
32
- const api = breadfruit({client: 'pg'});
33
- api.browse('tableName', [], {}, {orderBy: 'someColumn'})
34
- .catch((error) => {
35
- done();
36
- });
37
- });
38
-
39
- it('should fail to browse without a real connection', function(done) {
40
- const api = breadfruit({client: 'pg'});
41
- api.browse('tableName', [], {}, {orderBy: ['someColumn', 'otherColumn']})
42
- .catch((error) => {
43
- done();
44
- });
45
- });
46
-
47
- it('should fail to browse without a real connection', function(done) {
48
- const api = breadfruit({client: 'pg'});
49
- api.browse('tableName', [], {}, {orderBy: ['someColumn', 'otherColumn'], sortOrder: ['asc', 'desc']})
50
- .catch((error) => {
51
- done();
52
- });
53
- });
54
-
55
- it('should fail to read without a real connection', function(done) {
56
- const api = breadfruit({client: 'pg'});
57
- api.read('tableName', [], {})
58
- .catch((error) => {
59
- done();
60
- });
61
- });
62
-
63
- it('should fail to add without a real connection', function(done) {
64
- const api = breadfruit({client: 'pg'});
65
- api.add('tableName', [], {})
66
- .catch((error) => {
67
- done();
68
- });
69
- });
70
-
71
- it('should fail to edit without a real connection', function(done) {
72
- const api = breadfruit({client: 'pg'});
73
- api.edit('tableName', [], {})
74
- .catch((error) => {
75
- done();
76
- });
77
- });
78
-
79
- it('should fail to delete without a real connection', function(done) {
80
- const api = breadfruit({client: 'pg'});
81
- api.del('tableName', [], {})
82
- .catch((error) => {
83
- done();
84
- });
85
- });
86
-
87
- it('should fail to do raw query without a real connection', function(done) {
88
- const api = breadfruit({client: 'pg'});
89
- api.raw('select NOW()', {})
90
- .catch((error) => {
91
- done();
92
- });
93
- });
94
-
95
- });