rads-db 0.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 ADDED
@@ -0,0 +1,48 @@
1
+ # Introduction
2
+
3
+ When you work with the databases
4
+
5
+ ## What is ORM?
6
+
7
+ ORM is acronym for “Object Relational Mapping”. It is a library that helps developers to work with the database without
8
+ leaving comfort of their programming language.
9
+
10
+ In other words, if your database is e.g. SQL, instead of writing raw sql queries and parsing results, you can functions
11
+ provided by ORM library for your programming language (e.g. Typescript).
12
+
13
+ ```jsx
14
+ // Without ORM:
15
+ const data = db.query("select * from users where login like '%admin%'")
16
+ // With ORM:
17
+ const data = orm.users.get({ where: { login_contains: 'admin' } })
18
+ ```
19
+
20
+ Popular ORMs:
21
+
22
+ - Entity Framework (C#/.net)
23
+ - ServiceStack.OrmLite (C#/.net)
24
+ - Hibernate (Java)
25
+ - Prisma (TS)
26
+ - Drizzle (TS)
27
+ - TypeORM (TS)
28
+ - MikroORM (TS)
29
+ - Sequelize (TS)
30
+
31
+ [Here](https://www.prisma.io/docs/concepts/overview/prisma-in-your-stack/is-prisma-an-orm) is a good article from Prisma
32
+ providing detailed overview of how ORMs work.
33
+
34
+ ## What are the downsides of using ORM?
35
+
36
+ ORM hides database complexity from the developer, but that has a price.
37
+
38
+ - “Simple” ORMs (e.g. Prisma) quickly encounter limitations - e.g. sql queries that cannot be represented by ORM.
39
+ - “Complex” ORMs that try to support full set of features (e.g. Entity Framework) add their own complexity and introduce
40
+ mental load on top of managing SQL
41
+
42
+ Consensus among the developer community is to use “simple” ORM, and use raw queries when ORM capabilities is not enough.
43
+
44
+ ## Ok, ORMs are useful. Why create new one?
45
+
46
+ We wanted to have features that are not supported in current ORMs:
47
+
48
+ - [This](https://medium.com/@rogerpadillac/in-search-of-the-perfect-orm-e01fcc9bce3d) article gives good overview
package/dist/index.cjs ADDED
@@ -0,0 +1,327 @@
1
+ 'use strict';
2
+
3
+ const zod = require('zod');
4
+ const _ = require('lodash');
5
+ const pluralize = require('pluralize');
6
+
7
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
8
+
9
+ const ___default = /*#__PURE__*/_interopDefaultCompat(_);
10
+ const pluralize__default = /*#__PURE__*/_interopDefaultCompat(pluralize);
11
+
12
+ function generateValidators(schema) {
13
+ const zodSchemas = {};
14
+ for (const key in schema) {
15
+ if (!zodSchemas[key])
16
+ zodSchemas[key] = getZodSchema(zodSchemas, schema, key);
17
+ }
18
+ const result = {};
19
+ for (const key in zodSchemas) {
20
+ result[key] = (value) => zodSchemas[key].parse(value);
21
+ }
22
+ return result;
23
+ }
24
+ function getZodSchema(zodSchemas, schema, key) {
25
+ if (zodSchemas[key])
26
+ return zodSchemas[key];
27
+ const type = schema[key];
28
+ if (type.enumValues) {
29
+ const enums = ___default.fromPairs(type.enumValues.map((x) => [x, x]));
30
+ return zod.z.nativeEnum(enums);
31
+ }
32
+ const objectSchema = {};
33
+ for (const fieldName in type.fields) {
34
+ objectSchema[fieldName] = getFieldZodSchema(zodSchemas, schema, type.fields[fieldName]);
35
+ }
36
+ return zod.z.object(objectSchema);
37
+ }
38
+ function getFieldZodSchema(zodSchemas, schema, field) {
39
+ let fieldSchema = getFieldZodSchemaBase(zodSchemas, schema, field);
40
+ if (!field.isRequired)
41
+ fieldSchema = fieldSchema.optional();
42
+ if (field.defaultValue !== void 0)
43
+ fieldSchema = fieldSchema.default(field.defaultValue);
44
+ return fieldSchema;
45
+ }
46
+ function getFieldZodSchemaBase(zodSchemas, schema, field) {
47
+ if (field.type === "string")
48
+ return zod.z.string();
49
+ if (field.type === "boolean")
50
+ return zod.z.boolean();
51
+ if (field.type === "number")
52
+ return zod.z.number();
53
+ if (schema[field.type])
54
+ return getZodSchema(zodSchemas, schema, field.type);
55
+ throw new Error(`Unknown type: ${field.type}`);
56
+ }
57
+
58
+ const operatorFns = {
59
+ eq: (x, w) => x === w,
60
+ ieq: (x, w) => x?.toLowerCase() === w?.toLowerCase(),
61
+ in: (x, w) => w.includes(x),
62
+ startsWith: (x, w) => x?.startsWith(w),
63
+ istartsWith: (x, w) => x?.toLowerCase()?.startsWith(w.toLowerCase()),
64
+ endsWith: (x, w) => x?.endsWith(w),
65
+ iendsWith: (x, w) => x?.toLowerCase()?.endsWith(w.toLowerCase()),
66
+ contains: (x, w) => x?.includes(w),
67
+ icontains: (x, w) => x?.toLowerCase()?.includes(w.toLowerCase()),
68
+ isNull: (x, w) => {
69
+ if (w === true)
70
+ return x == null;
71
+ if (w === false)
72
+ return x != null;
73
+ return true;
74
+ },
75
+ gt: (x, w) => x > w,
76
+ gte: (x, w) => x >= w,
77
+ lt: (x, w) => x < w,
78
+ lte: (x, w) => x <= w,
79
+ between: (x, w) => {
80
+ const { start, end } = w || {};
81
+ let result = true;
82
+ if (start)
83
+ result = result && x >= start;
84
+ if (end)
85
+ result = result && x >= end;
86
+ return result;
87
+ }
88
+ };
89
+ const memory = (schema, entity) => {
90
+ const cache = {};
91
+ function getItemById(id) {
92
+ return cache[id] ?? null;
93
+ }
94
+ function getItemByIds(ids) {
95
+ return ids.map((id) => cache[id]);
96
+ }
97
+ return {
98
+ // getItemById,
99
+ // getItemByIds,
100
+ putMany(items) {
101
+ for (const item of items) {
102
+ if (!item?.id)
103
+ throw new Error(`You must provide an id`);
104
+ cache[item.id] = item;
105
+ }
106
+ },
107
+ getMany(args) {
108
+ args = args || {};
109
+ const where = args.where || {};
110
+ const whereKeys = ___default.keys(where);
111
+ if (whereKeys.length === 1) {
112
+ if (whereKeys[0] === "id")
113
+ return { nodes: [getItemById(where.id)].filter((x) => x), cursor: null };
114
+ if (whereKeys[0] === "id_in")
115
+ return { nodes: getItemByIds(where.id_in).filter((x) => x), cursor: null };
116
+ }
117
+ return queryArray(Object.values(cache), args);
118
+ }
119
+ };
120
+ };
121
+ function queryArray(array, args) {
122
+ if (!array)
123
+ return array;
124
+ let result = array;
125
+ const { where, orderByProperty, orderByDirection, maxItemCount, cursor } = prepareArgs(args);
126
+ const startIndex = Number(cursor) || 0;
127
+ const endIndex = startIndex + maxItemCount;
128
+ const f = getFilter(where);
129
+ if (f)
130
+ result = result.filter(f);
131
+ if (orderByProperty)
132
+ result = ___default.orderBy(result, [orderByProperty], [orderByDirection]);
133
+ if (maxItemCount)
134
+ result = result.slice(startIndex, endIndex);
135
+ const newCursor = endIndex >= array.length ? null : endIndex;
136
+ return { nodes: result, cursor: newCursor?.toString() || null };
137
+ }
138
+ function getFilter(where, namePrefix = "") {
139
+ if (___default.isEmpty(where))
140
+ return null;
141
+ const andClauses = [];
142
+ for (const key in where) {
143
+ const [nameFromWhere, operator] = getWhereNameOperatorPair(key);
144
+ const name = [namePrefix, nameFromWhere].filter((x) => x).join(".");
145
+ const whereVal = where[key];
146
+ if (whereVal == null)
147
+ continue;
148
+ const f = getFilterInner(operator, whereVal, name, namePrefix);
149
+ if (f)
150
+ andClauses.push(f);
151
+ }
152
+ if (andClauses.length === 0)
153
+ return null;
154
+ return (x) => andClauses.every((ac) => ac(x));
155
+ }
156
+ function getWhereNameOperatorPair(whereKey) {
157
+ if (whereKey.startsWith("_type")) {
158
+ const operator = whereKey.split("_")[2];
159
+ return ["_type", operator];
160
+ }
161
+ return whereKey.split("_");
162
+ }
163
+ function getFilterInner(operator, whereVal, name, namePrefix) {
164
+ if (!operator && ___default.isObject(whereVal)) {
165
+ return getFilter(whereVal, name);
166
+ }
167
+ if (operator === "some") {
168
+ const f = getFilter(whereVal, namePrefix);
169
+ return f && ((x) => {
170
+ const val = ___default.get(x, name);
171
+ return val === void 0 || val?.some(f);
172
+ });
173
+ }
174
+ if (operator === "none") {
175
+ const f = getFilter(whereVal, namePrefix);
176
+ return f && ((x) => !___default.get(x, name)?.some(f));
177
+ }
178
+ if (operator === "and") {
179
+ if (!___default.isArray(whereVal))
180
+ throw new Error(`Value for where._and must be an array`);
181
+ const newAndClauses = whereVal.map((whereValAnd) => getFilter(whereValAnd, namePrefix)).flatMap((x) => x ? [x] : []);
182
+ if (!newAndClauses.length)
183
+ return null;
184
+ return (x) => newAndClauses.every((ac) => ac(x));
185
+ }
186
+ if (operator === "not") {
187
+ const f = getFilter(whereVal, namePrefix);
188
+ return f && ((x) => !f(x));
189
+ }
190
+ if (operator === "or") {
191
+ if (!___default.isArray(whereVal))
192
+ throw new Error(`Value for where._or must be an array`);
193
+ const orClauses = whereVal.map((whereValOr) => getFilter(whereValOr, namePrefix)).flatMap((x) => x ? [x] : []);
194
+ if (!orClauses.length)
195
+ return null;
196
+ return (x) => orClauses.some((oc) => oc(x));
197
+ }
198
+ const operatorFn = operatorFns[operator || "eq"];
199
+ if (operatorFn) {
200
+ return (x) => operatorFn(___default.get(x, name), whereVal);
201
+ }
202
+ console.warn(`[rads-cache] Operator not supported: ${operator || "eq"}`);
203
+ return null;
204
+ }
205
+ function prepareArgs(args) {
206
+ args = args || {};
207
+ const where = { ...args.where };
208
+ let orderBy = args.orderBy || "";
209
+ if (Array.isArray(orderBy))
210
+ orderBy = orderBy[0] || "";
211
+ const orderByParts = orderBy.split("_");
212
+ const orderByProperty = orderByParts.slice(0, -1).join(".");
213
+ const orderByDirection = orderByParts.slice(-1)[0];
214
+ let maxItemCount = args.maxItemCount;
215
+ maxItemCount = maxItemCount || 100;
216
+ return { where, orderByProperty, orderByDirection, maxItemCount, cursor: args.cursor };
217
+ }
218
+
219
+ const restApi = (schema, entity, options) => {
220
+ options = { baseUrl: "/api", fetch: globalThis.fetch, ...options };
221
+ const fetch = options.fetch || global.fetch;
222
+ if (!options.fetch)
223
+ throw new Error('Please provide "fetch" argument to rest driver definition');
224
+ const pluralEntityName = ___default.lowerFirst(pluralize__default(entity));
225
+ return {
226
+ async getMany(args) {
227
+ const response = await fetch(`${options.baseUrl}/${pluralEntityName}`, {
228
+ method: "POST",
229
+ body: JSON.stringify(args)
230
+ });
231
+ return await response?.json();
232
+ },
233
+ async putMany(item) {
234
+ await fetch(`${options.baseUrl}/${pluralEntityName}`, {
235
+ method: "PUT",
236
+ body: JSON.stringify({ data: item })
237
+ });
238
+ }
239
+ };
240
+ };
241
+
242
+ const driverConstructors = { memory, restApi };
243
+ const drivers = {};
244
+ function generateMethods(schema, validators, args) {
245
+ const result = { _schema: schema };
246
+ for (const key in schema) {
247
+ if (!schema[key].decorators.entity)
248
+ continue;
249
+ const { handle } = schema[key];
250
+ if (!handle)
251
+ throw new Error(`Missing handle for entity ${key}`);
252
+ const driverInstance = getDriverInstance(schema, key, args?.driver || { type: "memory" });
253
+ result[handle] = {
254
+ get: async (args2) => {
255
+ return (await driverInstance.getMany({ ...args2, maxItemCount: 1 }))?.nodes[0] || null;
256
+ },
257
+ getMany: async (args2) => {
258
+ return driverInstance.getMany(args2 || {});
259
+ },
260
+ put: async (item) => {
261
+ console.log("validating", item);
262
+ item = validators[key](item);
263
+ await driverInstance.putMany([item]);
264
+ return item;
265
+ },
266
+ putMany: async (items) => {
267
+ console.log("validating", items);
268
+ const itemsToSave = items.map((item) => validators[key](item));
269
+ await driverInstance.putMany(itemsToSave);
270
+ return itemsToSave;
271
+ }
272
+ // putMany:
273
+ // deleteMany:
274
+ // updateMany:
275
+ // verifyMany:
276
+ };
277
+ }
278
+ return result;
279
+ }
280
+ function getDriverInstance(schema, key, driver) {
281
+ if (!drivers[key]) {
282
+ drivers[key] = getDriverInstanceInner(schema, key, driver);
283
+ }
284
+ return drivers[key];
285
+ }
286
+ function getDriverInstanceInner(schema, key, driver) {
287
+ const driverConstructor = driverConstructors[driver.type];
288
+ return driverConstructor(schema, key, driver);
289
+ }
290
+
291
+ function entity(meta) {
292
+ return function(classConstructor, _ctx) {
293
+ };
294
+ }
295
+ function field(meta) {
296
+ return function(a, b) {
297
+ };
298
+ }
299
+
300
+ function createRads(args) {
301
+ const { schema } = args;
302
+ const validators = generateValidators(schema);
303
+ return generateMethods(schema, validators, args);
304
+ }
305
+ function getRestRoutes(db, prefix = "/") {
306
+ const routes = {};
307
+ const schema = db._schema;
308
+ for (const key in schema) {
309
+ const entity = schema[key];
310
+ if (!entity.decorators?.entity)
311
+ continue;
312
+ routes[`${prefix}${entity.handle}`] = {
313
+ POST: (args) => db[entity.handle].get(args),
314
+ PUT: (args) => db[entity.handle].put(args?.data)
315
+ };
316
+ routes[`${prefix}${entity.handlePlural}`] = {
317
+ POST: (args) => db[entity.handle].getMany(args),
318
+ PUT: (args) => db[entity.handle].putMany(args?.data)
319
+ };
320
+ }
321
+ return routes;
322
+ }
323
+
324
+ exports.createRads = createRads;
325
+ exports.entity = entity;
326
+ exports.field = field;
327
+ exports.getRestRoutes = getRestRoutes;
@@ -0,0 +1,83 @@
1
+ import { HTTPMethod } from 'h3';
2
+
3
+ interface EntityDecoratorArgs {
4
+ driver?: string;
5
+ }
6
+ interface FieldDecoratorArgs {
7
+ relation?: Function;
8
+ }
9
+ interface CreateRadsArgs {
10
+ schema: Schema;
11
+ driver?: DriverOptions;
12
+ drivers?: Record<string, DriverOptions>;
13
+ defaultDriver?: string;
14
+ }
15
+ type Schema = Record<string, TypeDefinition>;
16
+ type SchemaValidators = Record<string, (item: any) => any>;
17
+ interface TypeDefinition {
18
+ name: string;
19
+ decorators: Record<string, Record<string, any>>;
20
+ fields?: Record<string, FieldDefinition>;
21
+ enumValues?: string[];
22
+ handle?: string;
23
+ handlePlural?: string;
24
+ }
25
+ type MaybePromise<T> = Promise<T> | T;
26
+ interface Driver {
27
+ putMany: <T extends Record<string, any>>(item: T[]) => MaybePromise<void>;
28
+ getMany: (args: GetManyArgs) => MaybePromise<{
29
+ nodes: Record<string, any>[];
30
+ cursor: string | null;
31
+ }>;
32
+ }
33
+ interface FieldDefinition {
34
+ name: string;
35
+ type: string;
36
+ defaultValue?: any;
37
+ isRequired?: boolean;
38
+ comment?: string;
39
+ }
40
+ interface GetManyArgs {
41
+ where?: Record<string, any>;
42
+ orderBy?: string;
43
+ maxItemCount?: number;
44
+ cursor?: string;
45
+ }
46
+ interface GetArgs {
47
+ where?: Record<string, any>;
48
+ }
49
+ interface EntityMethods<T = Record<string, any>> {
50
+ get: (args: GetArgs) => Promise<T>;
51
+ getMany: (args: GetManyArgs) => Promise<{
52
+ nodes: T[];
53
+ cursor: string | null;
54
+ }>;
55
+ put: (data: T) => Promise<T>;
56
+ putMany: (data: T[]) => Promise<T[]>;
57
+ }
58
+ type radsDb = Record<string, EntityMethods>;
59
+ interface RestDriverOptions {
60
+ type: 'restApi';
61
+ /** @default '/api' */
62
+ baseUrl?: string;
63
+ fetch?: (url: string, options?: {
64
+ body?: any;
65
+ headers?: any;
66
+ method?: HTTPMethod;
67
+ }) => any;
68
+ }
69
+ interface MemoryDriverOptions {
70
+ type: 'memory';
71
+ }
72
+ type DriverOptions = RestDriverOptions | MemoryDriverOptions;
73
+ interface RadsDb {
74
+ [key: string]: EntityMethods;
75
+ }
76
+
77
+ declare function entity(meta?: EntityDecoratorArgs): (classConstructor: Function, _ctx?: ClassDecoratorContext<any>) => void;
78
+ declare function field(meta?: FieldDecoratorArgs): (a: any, b?: ClassFieldDecoratorContext) => void;
79
+
80
+ declare function createRads(args: CreateRadsArgs): RadsDb;
81
+ declare function getRestRoutes(db: radsDb, prefix?: string): Record<string, Record<string, Function>>;
82
+
83
+ export { CreateRadsArgs, Driver, DriverOptions, EntityDecoratorArgs, EntityMethods, FieldDecoratorArgs, FieldDefinition, GetArgs, GetManyArgs, MemoryDriverOptions, RadsDb, RestDriverOptions, Schema, SchemaValidators, TypeDefinition, createRads, entity, field, getRestRoutes, radsDb };
package/dist/index.mjs ADDED
@@ -0,0 +1,317 @@
1
+ import { z } from 'zod';
2
+ import _ from 'lodash';
3
+ import pluralize from 'pluralize';
4
+
5
+ function generateValidators(schema) {
6
+ const zodSchemas = {};
7
+ for (const key in schema) {
8
+ if (!zodSchemas[key])
9
+ zodSchemas[key] = getZodSchema(zodSchemas, schema, key);
10
+ }
11
+ const result = {};
12
+ for (const key in zodSchemas) {
13
+ result[key] = (value) => zodSchemas[key].parse(value);
14
+ }
15
+ return result;
16
+ }
17
+ function getZodSchema(zodSchemas, schema, key) {
18
+ if (zodSchemas[key])
19
+ return zodSchemas[key];
20
+ const type = schema[key];
21
+ if (type.enumValues) {
22
+ const enums = _.fromPairs(type.enumValues.map((x) => [x, x]));
23
+ return z.nativeEnum(enums);
24
+ }
25
+ const objectSchema = {};
26
+ for (const fieldName in type.fields) {
27
+ objectSchema[fieldName] = getFieldZodSchema(zodSchemas, schema, type.fields[fieldName]);
28
+ }
29
+ return z.object(objectSchema);
30
+ }
31
+ function getFieldZodSchema(zodSchemas, schema, field) {
32
+ let fieldSchema = getFieldZodSchemaBase(zodSchemas, schema, field);
33
+ if (!field.isRequired)
34
+ fieldSchema = fieldSchema.optional();
35
+ if (field.defaultValue !== void 0)
36
+ fieldSchema = fieldSchema.default(field.defaultValue);
37
+ return fieldSchema;
38
+ }
39
+ function getFieldZodSchemaBase(zodSchemas, schema, field) {
40
+ if (field.type === "string")
41
+ return z.string();
42
+ if (field.type === "boolean")
43
+ return z.boolean();
44
+ if (field.type === "number")
45
+ return z.number();
46
+ if (schema[field.type])
47
+ return getZodSchema(zodSchemas, schema, field.type);
48
+ throw new Error(`Unknown type: ${field.type}`);
49
+ }
50
+
51
+ const operatorFns = {
52
+ eq: (x, w) => x === w,
53
+ ieq: (x, w) => x?.toLowerCase() === w?.toLowerCase(),
54
+ in: (x, w) => w.includes(x),
55
+ startsWith: (x, w) => x?.startsWith(w),
56
+ istartsWith: (x, w) => x?.toLowerCase()?.startsWith(w.toLowerCase()),
57
+ endsWith: (x, w) => x?.endsWith(w),
58
+ iendsWith: (x, w) => x?.toLowerCase()?.endsWith(w.toLowerCase()),
59
+ contains: (x, w) => x?.includes(w),
60
+ icontains: (x, w) => x?.toLowerCase()?.includes(w.toLowerCase()),
61
+ isNull: (x, w) => {
62
+ if (w === true)
63
+ return x == null;
64
+ if (w === false)
65
+ return x != null;
66
+ return true;
67
+ },
68
+ gt: (x, w) => x > w,
69
+ gte: (x, w) => x >= w,
70
+ lt: (x, w) => x < w,
71
+ lte: (x, w) => x <= w,
72
+ between: (x, w) => {
73
+ const { start, end } = w || {};
74
+ let result = true;
75
+ if (start)
76
+ result = result && x >= start;
77
+ if (end)
78
+ result = result && x >= end;
79
+ return result;
80
+ }
81
+ };
82
+ const memory = (schema, entity) => {
83
+ const cache = {};
84
+ function getItemById(id) {
85
+ return cache[id] ?? null;
86
+ }
87
+ function getItemByIds(ids) {
88
+ return ids.map((id) => cache[id]);
89
+ }
90
+ return {
91
+ // getItemById,
92
+ // getItemByIds,
93
+ putMany(items) {
94
+ for (const item of items) {
95
+ if (!item?.id)
96
+ throw new Error(`You must provide an id`);
97
+ cache[item.id] = item;
98
+ }
99
+ },
100
+ getMany(args) {
101
+ args = args || {};
102
+ const where = args.where || {};
103
+ const whereKeys = _.keys(where);
104
+ if (whereKeys.length === 1) {
105
+ if (whereKeys[0] === "id")
106
+ return { nodes: [getItemById(where.id)].filter((x) => x), cursor: null };
107
+ if (whereKeys[0] === "id_in")
108
+ return { nodes: getItemByIds(where.id_in).filter((x) => x), cursor: null };
109
+ }
110
+ return queryArray(Object.values(cache), args);
111
+ }
112
+ };
113
+ };
114
+ function queryArray(array, args) {
115
+ if (!array)
116
+ return array;
117
+ let result = array;
118
+ const { where, orderByProperty, orderByDirection, maxItemCount, cursor } = prepareArgs(args);
119
+ const startIndex = Number(cursor) || 0;
120
+ const endIndex = startIndex + maxItemCount;
121
+ const f = getFilter(where);
122
+ if (f)
123
+ result = result.filter(f);
124
+ if (orderByProperty)
125
+ result = _.orderBy(result, [orderByProperty], [orderByDirection]);
126
+ if (maxItemCount)
127
+ result = result.slice(startIndex, endIndex);
128
+ const newCursor = endIndex >= array.length ? null : endIndex;
129
+ return { nodes: result, cursor: newCursor?.toString() || null };
130
+ }
131
+ function getFilter(where, namePrefix = "") {
132
+ if (_.isEmpty(where))
133
+ return null;
134
+ const andClauses = [];
135
+ for (const key in where) {
136
+ const [nameFromWhere, operator] = getWhereNameOperatorPair(key);
137
+ const name = [namePrefix, nameFromWhere].filter((x) => x).join(".");
138
+ const whereVal = where[key];
139
+ if (whereVal == null)
140
+ continue;
141
+ const f = getFilterInner(operator, whereVal, name, namePrefix);
142
+ if (f)
143
+ andClauses.push(f);
144
+ }
145
+ if (andClauses.length === 0)
146
+ return null;
147
+ return (x) => andClauses.every((ac) => ac(x));
148
+ }
149
+ function getWhereNameOperatorPair(whereKey) {
150
+ if (whereKey.startsWith("_type")) {
151
+ const operator = whereKey.split("_")[2];
152
+ return ["_type", operator];
153
+ }
154
+ return whereKey.split("_");
155
+ }
156
+ function getFilterInner(operator, whereVal, name, namePrefix) {
157
+ if (!operator && _.isObject(whereVal)) {
158
+ return getFilter(whereVal, name);
159
+ }
160
+ if (operator === "some") {
161
+ const f = getFilter(whereVal, namePrefix);
162
+ return f && ((x) => {
163
+ const val = _.get(x, name);
164
+ return val === void 0 || val?.some(f);
165
+ });
166
+ }
167
+ if (operator === "none") {
168
+ const f = getFilter(whereVal, namePrefix);
169
+ return f && ((x) => !_.get(x, name)?.some(f));
170
+ }
171
+ if (operator === "and") {
172
+ if (!_.isArray(whereVal))
173
+ throw new Error(`Value for where._and must be an array`);
174
+ const newAndClauses = whereVal.map((whereValAnd) => getFilter(whereValAnd, namePrefix)).flatMap((x) => x ? [x] : []);
175
+ if (!newAndClauses.length)
176
+ return null;
177
+ return (x) => newAndClauses.every((ac) => ac(x));
178
+ }
179
+ if (operator === "not") {
180
+ const f = getFilter(whereVal, namePrefix);
181
+ return f && ((x) => !f(x));
182
+ }
183
+ if (operator === "or") {
184
+ if (!_.isArray(whereVal))
185
+ throw new Error(`Value for where._or must be an array`);
186
+ const orClauses = whereVal.map((whereValOr) => getFilter(whereValOr, namePrefix)).flatMap((x) => x ? [x] : []);
187
+ if (!orClauses.length)
188
+ return null;
189
+ return (x) => orClauses.some((oc) => oc(x));
190
+ }
191
+ const operatorFn = operatorFns[operator || "eq"];
192
+ if (operatorFn) {
193
+ return (x) => operatorFn(_.get(x, name), whereVal);
194
+ }
195
+ console.warn(`[rads-cache] Operator not supported: ${operator || "eq"}`);
196
+ return null;
197
+ }
198
+ function prepareArgs(args) {
199
+ args = args || {};
200
+ const where = { ...args.where };
201
+ let orderBy = args.orderBy || "";
202
+ if (Array.isArray(orderBy))
203
+ orderBy = orderBy[0] || "";
204
+ const orderByParts = orderBy.split("_");
205
+ const orderByProperty = orderByParts.slice(0, -1).join(".");
206
+ const orderByDirection = orderByParts.slice(-1)[0];
207
+ let maxItemCount = args.maxItemCount;
208
+ maxItemCount = maxItemCount || 100;
209
+ return { where, orderByProperty, orderByDirection, maxItemCount, cursor: args.cursor };
210
+ }
211
+
212
+ const restApi = (schema, entity, options) => {
213
+ options = { baseUrl: "/api", fetch: globalThis.fetch, ...options };
214
+ const fetch = options.fetch || global.fetch;
215
+ if (!options.fetch)
216
+ throw new Error('Please provide "fetch" argument to rest driver definition');
217
+ const pluralEntityName = _.lowerFirst(pluralize(entity));
218
+ return {
219
+ async getMany(args) {
220
+ const response = await fetch(`${options.baseUrl}/${pluralEntityName}`, {
221
+ method: "POST",
222
+ body: JSON.stringify(args)
223
+ });
224
+ return await response?.json();
225
+ },
226
+ async putMany(item) {
227
+ await fetch(`${options.baseUrl}/${pluralEntityName}`, {
228
+ method: "PUT",
229
+ body: JSON.stringify({ data: item })
230
+ });
231
+ }
232
+ };
233
+ };
234
+
235
+ const driverConstructors = { memory, restApi };
236
+ const drivers = {};
237
+ function generateMethods(schema, validators, args) {
238
+ const result = { _schema: schema };
239
+ for (const key in schema) {
240
+ if (!schema[key].decorators.entity)
241
+ continue;
242
+ const { handle } = schema[key];
243
+ if (!handle)
244
+ throw new Error(`Missing handle for entity ${key}`);
245
+ const driverInstance = getDriverInstance(schema, key, args?.driver || { type: "memory" });
246
+ result[handle] = {
247
+ get: async (args2) => {
248
+ return (await driverInstance.getMany({ ...args2, maxItemCount: 1 }))?.nodes[0] || null;
249
+ },
250
+ getMany: async (args2) => {
251
+ return driverInstance.getMany(args2 || {});
252
+ },
253
+ put: async (item) => {
254
+ console.log("validating", item);
255
+ item = validators[key](item);
256
+ await driverInstance.putMany([item]);
257
+ return item;
258
+ },
259
+ putMany: async (items) => {
260
+ console.log("validating", items);
261
+ const itemsToSave = items.map((item) => validators[key](item));
262
+ await driverInstance.putMany(itemsToSave);
263
+ return itemsToSave;
264
+ }
265
+ // putMany:
266
+ // deleteMany:
267
+ // updateMany:
268
+ // verifyMany:
269
+ };
270
+ }
271
+ return result;
272
+ }
273
+ function getDriverInstance(schema, key, driver) {
274
+ if (!drivers[key]) {
275
+ drivers[key] = getDriverInstanceInner(schema, key, driver);
276
+ }
277
+ return drivers[key];
278
+ }
279
+ function getDriverInstanceInner(schema, key, driver) {
280
+ const driverConstructor = driverConstructors[driver.type];
281
+ return driverConstructor(schema, key, driver);
282
+ }
283
+
284
+ function entity(meta) {
285
+ return function(classConstructor, _ctx) {
286
+ };
287
+ }
288
+ function field(meta) {
289
+ return function(a, b) {
290
+ };
291
+ }
292
+
293
+ function createRads(args) {
294
+ const { schema } = args;
295
+ const validators = generateValidators(schema);
296
+ return generateMethods(schema, validators, args);
297
+ }
298
+ function getRestRoutes(db, prefix = "/") {
299
+ const routes = {};
300
+ const schema = db._schema;
301
+ for (const key in schema) {
302
+ const entity = schema[key];
303
+ if (!entity.decorators?.entity)
304
+ continue;
305
+ routes[`${prefix}${entity.handle}`] = {
306
+ POST: (args) => db[entity.handle].get(args),
307
+ PUT: (args) => db[entity.handle].put(args?.data)
308
+ };
309
+ routes[`${prefix}${entity.handlePlural}`] = {
310
+ POST: (args) => db[entity.handle].getMany(args),
311
+ PUT: (args) => db[entity.handle].putMany(args?.data)
312
+ };
313
+ }
314
+ return routes;
315
+ }
316
+
317
+ export { createRads, entity, field, getRestRoutes };
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "rads-db",
3
+ "files": [
4
+ "dist"
5
+ ],
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.mjs",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./drivers/*": {
16
+ "types": "./drivers/*.d.ts",
17
+ "import": "./drivers/*.mjs",
18
+ "require": "./drivers/*.cjs"
19
+ },
20
+ "./integrations/*": {
21
+ "types": "./integrations/*.d.ts",
22
+ "import": "./integrations/*.mjs",
23
+ "require": "./integrations/*.cjs"
24
+ }
25
+ },
26
+ "version": "0.1.0",
27
+ "description": "Say goodbye to boilerplate code and hello to efficient and elegant syntax.",
28
+ "keywords": [],
29
+ "author": "",
30
+ "license": "ISC",
31
+ "peerDependencies": {
32
+ "h3": "*",
33
+ "typescript": ">=5.0.0"
34
+ },
35
+ "devDependencies": {
36
+ "@vitest/ui": "^0.31.0",
37
+ "tsup": "^6.7.0",
38
+ "unbuild": "^1.2.1",
39
+ "vite": "^4.0.0",
40
+ "vitest": "^0.31.0"
41
+ },
42
+ "dependencies": {
43
+ "@azure/cosmos": "^3.17.3",
44
+ "@nuxt/kit": "^3.5.1",
45
+ "@types/lodash": "4.14.191",
46
+ "dataloader": "^2.2.2",
47
+ "lodash": "link:@types\\lodash",
48
+ "pluralize": "^8.0.0",
49
+ "rads-db": "^0.1.0",
50
+ "zod": "^3.21.4"
51
+ },
52
+ "scripts": {
53
+ "test": "vitest",
54
+ "dev": "pnpm install --frozen-lockfile && vitest --ui",
55
+ "build": "unbuild"
56
+ }
57
+ }