@cedarjs/record 0.0.4
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/LICENSE +21 -0
- package/README.md +35 -0
- package/dist/cjs/errors.js +75 -0
- package/dist/cjs/index.js +54 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/redwoodrecord/Core.js +265 -0
- package/dist/cjs/redwoodrecord/RedwoodRecord.js +65 -0
- package/dist/cjs/redwoodrecord/Reflection.js +100 -0
- package/dist/cjs/redwoodrecord/RelationProxy.js +138 -0
- package/dist/cjs/redwoodrecord/ValidationMixin.js +92 -0
- package/dist/cjs/tasks/parse.js +121 -0
- package/dist/errors.js +47 -0
- package/dist/index.js +14 -0
- package/dist/redwoodrecord/Core.js +236 -0
- package/dist/redwoodrecord/RedwoodRecord.js +36 -0
- package/dist/redwoodrecord/Reflection.js +81 -0
- package/dist/redwoodrecord/RelationProxy.js +109 -0
- package/dist/redwoodrecord/ValidationMixin.js +73 -0
- package/dist/tasks/parse.js +88 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Cedar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# RedwoodRecord
|
|
2
|
+
|
|
3
|
+
RedwoodRecord is an ORM ([Object-Relational Mapping](https://en.wikipedia.org/wiki/Object%E2%80%93relational_mapping)) built on top of Prisma. It may be extended in the future to wrap other database access packages.
|
|
4
|
+
|
|
5
|
+
RedwoodRecord is heavily inspired by [ActiveRecord](https://guides.rubyonrails.org/active_record_basics.html) which ships with [Ruby on Rails](https://rubyonrails.org). It presents a natural interface to the underlying data in your database, without worry about the particulars of SQL syntax.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
Usage documentation is available at https://redwoodjs.com/docs/redwoodrecord
|
|
10
|
+
|
|
11
|
+
## Package Structure
|
|
12
|
+
|
|
13
|
+
RedwoodRecord is composed of 5 classes total:
|
|
14
|
+
|
|
15
|
+
1. **Core** - provides core database access for a single model, enabling CRUD actions
|
|
16
|
+
2. **Reflection** - parses the Prisma data schema and determines what relations exist to other models
|
|
17
|
+
3. **RelationProxy** - provides access to related models reveales by the Reflection class
|
|
18
|
+
4. **ValidationMixin** - adds validation support, adding error messages
|
|
19
|
+
5. **RedwoodRecord** - combines the functionality of the other four classes into the standard base class that all models should extend from
|
|
20
|
+
|
|
21
|
+
There is also an `errors.js` file which contains all error messages that RedwoodRecord could throw.
|
|
22
|
+
|
|
23
|
+
### Tasks
|
|
24
|
+
|
|
25
|
+
RedwoodRecord depends on reading the Prisma schema file to determine relations to other tables. In order to avoid the `async` wait when parsing the schema with Prisma's built-in function, we have a task which parses the schema file and then saves out a JSON version to the cache `.redwood` directory. This task also creates an `index.js` file in your `api/src/models` directory that imports the models themselves and also adds some configuration to support relations without a circular dependency. The models are then re-exported.
|
|
26
|
+
|
|
27
|
+
This task is run with:
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
yarn rw record init
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
This task needs to be run whenever you create a new model, or change your database schema in a way that would add/remove a relation to another model.
|
|
34
|
+
|
|
35
|
+
> We plan to make the task run automatically, watching for changes to your schema.prisma or models directory. But for now you'll need to run it manually!
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
var errors_exports = {};
|
|
19
|
+
__export(errors_exports, {
|
|
20
|
+
RedwoodRecordError: () => RedwoodRecordError,
|
|
21
|
+
RedwoodRecordMissingAttributeError: () => RedwoodRecordMissingAttributeError,
|
|
22
|
+
RedwoodRecordMissingRequiredModelError: () => RedwoodRecordMissingRequiredModelError,
|
|
23
|
+
RedwoodRecordNotFoundError: () => RedwoodRecordNotFoundError,
|
|
24
|
+
RedwoodRecordNullAttributeError: () => RedwoodRecordNullAttributeError,
|
|
25
|
+
RedwoodRecordUncaughtError: () => RedwoodRecordUncaughtError
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(errors_exports);
|
|
28
|
+
var import_api = require("@cedarjs/api");
|
|
29
|
+
class RedwoodRecordError extends import_api.RedwoodError {
|
|
30
|
+
constructor() {
|
|
31
|
+
super();
|
|
32
|
+
this.name = "RedwoodRecordError";
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
class RedwoodRecordUncaughtError extends import_api.RedwoodError {
|
|
36
|
+
constructor(message) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.name = "RedwoodRecordUncaughtError";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
class RedwoodRecordNotFoundError extends import_api.RedwoodError {
|
|
42
|
+
constructor(name) {
|
|
43
|
+
super(`${name} record not found`);
|
|
44
|
+
this.name = "RedwoodRecordNotFoundError";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
class RedwoodRecordNullAttributeError extends import_api.RedwoodError {
|
|
48
|
+
constructor(name) {
|
|
49
|
+
super(`${name} must not be null`);
|
|
50
|
+
this.name = "RedwoodRecordNullAttributeError";
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
class RedwoodRecordMissingAttributeError extends import_api.RedwoodError {
|
|
54
|
+
constructor(name) {
|
|
55
|
+
super(`${name} is missing`);
|
|
56
|
+
this.name = "RedwoodRecordMissingAttributeError";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
class RedwoodRecordMissingRequiredModelError extends import_api.RedwoodError {
|
|
60
|
+
constructor(modelName, requiredModelName) {
|
|
61
|
+
super(
|
|
62
|
+
`Tried to build a relationship for ${requiredModelName} model but is not listed as a \`requiredModel\` in ${modelName}`
|
|
63
|
+
);
|
|
64
|
+
this.name = "RedwoodRecordMissingRequiredModelError";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
68
|
+
0 && (module.exports = {
|
|
69
|
+
RedwoodRecordError,
|
|
70
|
+
RedwoodRecordMissingAttributeError,
|
|
71
|
+
RedwoodRecordMissingRequiredModelError,
|
|
72
|
+
RedwoodRecordNotFoundError,
|
|
73
|
+
RedwoodRecordNullAttributeError,
|
|
74
|
+
RedwoodRecordUncaughtError
|
|
75
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
var index_exports = {};
|
|
30
|
+
__export(index_exports, {
|
|
31
|
+
Core: () => import_Core.default,
|
|
32
|
+
RedwoodRecord: () => import_RedwoodRecord.default,
|
|
33
|
+
Reflection: () => import_Reflection.default,
|
|
34
|
+
RelationProxy: () => import_RelationProxy.default,
|
|
35
|
+
ValidationMixin: () => import_ValidationMixin.default
|
|
36
|
+
});
|
|
37
|
+
module.exports = __toCommonJS(index_exports);
|
|
38
|
+
__reExport(index_exports, require("./errors"), module.exports);
|
|
39
|
+
var import_Core = __toESM(require("./redwoodrecord/Core"), 1);
|
|
40
|
+
var import_Reflection = __toESM(require("./redwoodrecord/Reflection"), 1);
|
|
41
|
+
var import_RelationProxy = __toESM(require("./redwoodrecord/RelationProxy"), 1);
|
|
42
|
+
var import_ValidationMixin = __toESM(require("./redwoodrecord/ValidationMixin"), 1);
|
|
43
|
+
var import_RedwoodRecord = __toESM(require("./redwoodrecord/RedwoodRecord"), 1);
|
|
44
|
+
__reExport(index_exports, require("./tasks/parse"), module.exports);
|
|
45
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
46
|
+
0 && (module.exports = {
|
|
47
|
+
Core,
|
|
48
|
+
RedwoodRecord,
|
|
49
|
+
Reflection,
|
|
50
|
+
RelationProxy,
|
|
51
|
+
ValidationMixin,
|
|
52
|
+
...require("./errors"),
|
|
53
|
+
...require("./tasks/parse")
|
|
54
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var Core_exports = {};
|
|
29
|
+
__export(Core_exports, {
|
|
30
|
+
default: () => Core
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(Core_exports);
|
|
33
|
+
var import_camelcase = __toESM(require("camelcase"), 1);
|
|
34
|
+
var Errors = __toESM(require("../errors"), 1);
|
|
35
|
+
class Core {
|
|
36
|
+
////////////////////////////
|
|
37
|
+
// Public class properties
|
|
38
|
+
////////////////////////////
|
|
39
|
+
// Set in child class to override DB accessor name. This is the name of the
|
|
40
|
+
// property you would call on an instance of Prisma Client in order the query
|
|
41
|
+
// a model in your schema. i.e. For the call `db.user` the accessorName is
|
|
42
|
+
// "user". Not setting this property will use the default camelCase version of
|
|
43
|
+
// the class name itself as the accessor.
|
|
44
|
+
//
|
|
45
|
+
// static accessorName = 'users'
|
|
46
|
+
static accessorName;
|
|
47
|
+
// Set in child class to override primary key field name for this model.
|
|
48
|
+
//
|
|
49
|
+
// static primaryKey = 'userId'
|
|
50
|
+
static primaryKey = "id";
|
|
51
|
+
// Parsed schema.prisma for use in subclasses
|
|
52
|
+
static schema = null;
|
|
53
|
+
// Any other model that may be required by this model
|
|
54
|
+
static requiredModels = [];
|
|
55
|
+
/////////////////////////
|
|
56
|
+
// Public class methods
|
|
57
|
+
/////////////////////////
|
|
58
|
+
// Access the raw Prisma Client for doing low level query manipulation
|
|
59
|
+
static db = null;
|
|
60
|
+
// Returns the Prisma DB accessor instance (ex. db.user)
|
|
61
|
+
static get accessor() {
|
|
62
|
+
return this.db[this.accessorName || (0, import_camelcase.default)(this.name)];
|
|
63
|
+
}
|
|
64
|
+
// Alias for where({})
|
|
65
|
+
static all(args) {
|
|
66
|
+
return this.where({}, args);
|
|
67
|
+
}
|
|
68
|
+
static build(attributes) {
|
|
69
|
+
const record = new this();
|
|
70
|
+
record.attributes = attributes;
|
|
71
|
+
return record;
|
|
72
|
+
}
|
|
73
|
+
// Create a new record. Instantiates a new instance and then calls .save() on it
|
|
74
|
+
static async create(attributes, options = {}) {
|
|
75
|
+
const record = this.build(attributes);
|
|
76
|
+
return await record.save(options);
|
|
77
|
+
}
|
|
78
|
+
// Find a single record by ID.
|
|
79
|
+
static async find(id, options = {}) {
|
|
80
|
+
const record = await this.findBy(
|
|
81
|
+
{
|
|
82
|
+
[this.primaryKey]: id,
|
|
83
|
+
...options.where || {}
|
|
84
|
+
},
|
|
85
|
+
options
|
|
86
|
+
);
|
|
87
|
+
if (record === null) {
|
|
88
|
+
throw new Errors.RedwoodRecordNotFoundError(this.name);
|
|
89
|
+
}
|
|
90
|
+
return record;
|
|
91
|
+
}
|
|
92
|
+
// Returns the first record matching the given `where`, otherwise first in the
|
|
93
|
+
// whole table (whatever the DB determines is the first record)
|
|
94
|
+
static async findBy(attributes, options = {}) {
|
|
95
|
+
const record = await this.accessor.findFirst({
|
|
96
|
+
where: attributes,
|
|
97
|
+
...options
|
|
98
|
+
});
|
|
99
|
+
return record ? await this.build(record) : null;
|
|
100
|
+
}
|
|
101
|
+
// Alias for findBy
|
|
102
|
+
static async first(...args) {
|
|
103
|
+
return this.findBy(...args);
|
|
104
|
+
}
|
|
105
|
+
// Find all records
|
|
106
|
+
static async where(attributes, options = {}) {
|
|
107
|
+
const records = await this.accessor.findMany({
|
|
108
|
+
where: attributes,
|
|
109
|
+
...options
|
|
110
|
+
});
|
|
111
|
+
return Promise.all(
|
|
112
|
+
records.map(async (record) => {
|
|
113
|
+
return await this.build(record);
|
|
114
|
+
})
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
///////////////////////////////
|
|
118
|
+
// Private instance properties
|
|
119
|
+
///////////////////////////////
|
|
120
|
+
// Stores instance attributes object internally
|
|
121
|
+
#attributes = {};
|
|
122
|
+
////////////////////////////
|
|
123
|
+
// Public instance methods
|
|
124
|
+
////////////////////////////
|
|
125
|
+
constructor() {
|
|
126
|
+
}
|
|
127
|
+
get attributes() {
|
|
128
|
+
return this.#attributes;
|
|
129
|
+
}
|
|
130
|
+
set attributes(attrs) {
|
|
131
|
+
if (attrs) {
|
|
132
|
+
this.#attributes = attrs;
|
|
133
|
+
this._createPropertiesForAttributes();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async destroy(options = {}) {
|
|
137
|
+
const { throw: shouldThrow, ...rest } = options;
|
|
138
|
+
try {
|
|
139
|
+
await this.constructor.accessor.delete({
|
|
140
|
+
where: { [this.constructor.primaryKey]: this.attributes.id },
|
|
141
|
+
...rest
|
|
142
|
+
});
|
|
143
|
+
return this;
|
|
144
|
+
} catch (e) {
|
|
145
|
+
this._deleteErrorHandler(e, shouldThrow);
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// Saves the attributes to the database
|
|
150
|
+
async save(options = {}) {
|
|
151
|
+
const saveAttributes = JSON.parse(JSON.stringify(this.attributes));
|
|
152
|
+
try {
|
|
153
|
+
let newAttributes;
|
|
154
|
+
if (this.attributes[this.constructor.primaryKey]) {
|
|
155
|
+
delete saveAttributes[this.constructor.primaryKey];
|
|
156
|
+
newAttributes = await this.constructor.accessor.update({
|
|
157
|
+
where: {
|
|
158
|
+
[this.constructor.primaryKey]: this.attributes[this.constructor.primaryKey]
|
|
159
|
+
},
|
|
160
|
+
data: saveAttributes
|
|
161
|
+
});
|
|
162
|
+
} else {
|
|
163
|
+
newAttributes = await this.constructor.accessor.create({
|
|
164
|
+
data: saveAttributes
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
this.attributes = newAttributes;
|
|
168
|
+
} catch (e) {
|
|
169
|
+
this._saveErrorHandler(e, options.throw);
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
174
|
+
async update(attributes = {}, options = {}) {
|
|
175
|
+
this.#attributes = Object.assign(this.#attributes, attributes);
|
|
176
|
+
return await this.save(options);
|
|
177
|
+
}
|
|
178
|
+
////////////////////////////
|
|
179
|
+
// Private instance methods
|
|
180
|
+
////////////////////////////
|
|
181
|
+
// Turns a plain object's properties into getters/setters on the instance:
|
|
182
|
+
//
|
|
183
|
+
// const user = new User({ name: 'Rob' })
|
|
184
|
+
// user.name // => 'Rob'
|
|
185
|
+
_createPropertiesForAttributes() {
|
|
186
|
+
for (const [name, value] of Object.entries(this.attributes)) {
|
|
187
|
+
if (this.hasOwnProperty(name)) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (value && typeof value === "object" && (Object.keys(value).includes("create") || Object.keys(value).includes("connect"))) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
this._createPropertyForAttribute(name);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
// Create property for a single attribute
|
|
197
|
+
_createPropertyForAttribute(name) {
|
|
198
|
+
Object.defineProperty(this, name, {
|
|
199
|
+
get() {
|
|
200
|
+
return this._attributeGetter(name);
|
|
201
|
+
},
|
|
202
|
+
set(value) {
|
|
203
|
+
this._attributeSetter(name, value);
|
|
204
|
+
},
|
|
205
|
+
enumerable: true
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
_attributeGetter(name) {
|
|
209
|
+
return this.#attributes[name];
|
|
210
|
+
}
|
|
211
|
+
_attributeSetter(name, value) {
|
|
212
|
+
return this.#attributes[name] = value;
|
|
213
|
+
}
|
|
214
|
+
_onSaveError(_name, _message) {
|
|
215
|
+
}
|
|
216
|
+
_deleteErrorHandler(error, shouldThrow) {
|
|
217
|
+
if (error.message.match(/Record to delete does not exist/)) {
|
|
218
|
+
this._onSaveError(
|
|
219
|
+
"base",
|
|
220
|
+
`${this.constructor.name} record to destroy not found`
|
|
221
|
+
);
|
|
222
|
+
if (shouldThrow) {
|
|
223
|
+
throw new Errors.RedwoodRecordNotFoundError();
|
|
224
|
+
}
|
|
225
|
+
} else {
|
|
226
|
+
this._onSaveError(
|
|
227
|
+
"base",
|
|
228
|
+
`${this.constructor.name} record threw uncaught error: ${error.message}`
|
|
229
|
+
);
|
|
230
|
+
if (shouldThrow) {
|
|
231
|
+
throw new Errors.RedwoodRecordUncaughtError(error.message);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
// Handles errors from saving a record (either update or create), converting
|
|
236
|
+
// to this.#errors messages, or throwing RedwoodRecord errors
|
|
237
|
+
_saveErrorHandler(error, shouldThrow) {
|
|
238
|
+
if (error.message.match(/Record to update not found/)) {
|
|
239
|
+
this._onSaveError(
|
|
240
|
+
"base",
|
|
241
|
+
`${this.constructor.name} record to update not found`
|
|
242
|
+
);
|
|
243
|
+
if (shouldThrow) {
|
|
244
|
+
throw new Errors.RedwoodRecordNotFoundError(this.constructor.name);
|
|
245
|
+
}
|
|
246
|
+
} else if (error.message.match(/must not be null/)) {
|
|
247
|
+
const [_all, name] = error.message.match(/Argument (\w+)/);
|
|
248
|
+
this._onSaveError(name, "must not be null");
|
|
249
|
+
if (shouldThrow) {
|
|
250
|
+
throw new Errors.RedwoodRecordNullAttributeError(name);
|
|
251
|
+
}
|
|
252
|
+
} else if (error.message.match(/is missing/)) {
|
|
253
|
+
const [_all, name] = error.message.match(/Argument (\w+)/);
|
|
254
|
+
this._onSaveError("base", `${name} is missing`);
|
|
255
|
+
if (shouldThrow) {
|
|
256
|
+
throw new Errors.RedwoodRecordMissingAttributeError(name);
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
this._onSaveError("base", error.message);
|
|
260
|
+
if (shouldThrow) {
|
|
261
|
+
throw new Errors.RedwoodRecordUncaughtError(error.message);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
20
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
21
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
22
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
23
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
24
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
25
|
+
mod
|
|
26
|
+
));
|
|
27
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
28
|
+
var RedwoodRecord_exports = {};
|
|
29
|
+
__export(RedwoodRecord_exports, {
|
|
30
|
+
default: () => RedwoodRecord
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(RedwoodRecord_exports);
|
|
33
|
+
var import_Core = __toESM(require("./Core"), 1);
|
|
34
|
+
var import_Reflection = __toESM(require("./Reflection"), 1);
|
|
35
|
+
var import_RelationProxy = __toESM(require("./RelationProxy"), 1);
|
|
36
|
+
var import_ValidationMixin = __toESM(require("./ValidationMixin"), 1);
|
|
37
|
+
class RedwoodRecord extends (0, import_ValidationMixin.default)(import_Core.default) {
|
|
38
|
+
static get reflect() {
|
|
39
|
+
return new import_Reflection.default(this);
|
|
40
|
+
}
|
|
41
|
+
// Call original build, but add related attributes
|
|
42
|
+
static build(attributes) {
|
|
43
|
+
const record = super.build(attributes);
|
|
44
|
+
import_RelationProxy.default.addRelations(record, this.constructor.schema);
|
|
45
|
+
return record;
|
|
46
|
+
}
|
|
47
|
+
// Don't even try to save if data isn't valid
|
|
48
|
+
async save(options = {}) {
|
|
49
|
+
if (this.validate({ throw: options.throw })) {
|
|
50
|
+
return await super.save(options);
|
|
51
|
+
} else {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
// Call original method, but add error keys for validation
|
|
56
|
+
_createPropertyForAttribute(name) {
|
|
57
|
+
super._createPropertyForAttribute(name);
|
|
58
|
+
this._errors[name] = [];
|
|
59
|
+
}
|
|
60
|
+
// Add validation error on save error
|
|
61
|
+
_onSaveError(...args) {
|
|
62
|
+
super._onSaveError(...args);
|
|
63
|
+
this.addError(...args);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __export = (target, all) => {
|
|
6
|
+
for (var name in all)
|
|
7
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
+
};
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (let key of __getOwnPropNames(from))
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
+
}
|
|
15
|
+
return to;
|
|
16
|
+
};
|
|
17
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
+
var Reflection_exports = {};
|
|
19
|
+
__export(Reflection_exports, {
|
|
20
|
+
default: () => Reflection
|
|
21
|
+
});
|
|
22
|
+
module.exports = __toCommonJS(Reflection_exports);
|
|
23
|
+
class Reflection {
|
|
24
|
+
#hasMany = null;
|
|
25
|
+
#belongsTo = null;
|
|
26
|
+
#attributes = null;
|
|
27
|
+
constructor(model) {
|
|
28
|
+
this.model = model;
|
|
29
|
+
}
|
|
30
|
+
get attributes() {
|
|
31
|
+
if (!this.#attributes) {
|
|
32
|
+
this.#parseAttributes();
|
|
33
|
+
}
|
|
34
|
+
return this.#attributes;
|
|
35
|
+
}
|
|
36
|
+
get belongsTo() {
|
|
37
|
+
if (!this.#belongsTo) {
|
|
38
|
+
this.#parseBelongsTo();
|
|
39
|
+
}
|
|
40
|
+
return this.#belongsTo;
|
|
41
|
+
}
|
|
42
|
+
get hasMany() {
|
|
43
|
+
if (!this.#hasMany) {
|
|
44
|
+
this.#parseHasMany();
|
|
45
|
+
}
|
|
46
|
+
return this.#hasMany;
|
|
47
|
+
}
|
|
48
|
+
// Finds the schema for a single model
|
|
49
|
+
#schemaForModel(name = this.model.name) {
|
|
50
|
+
return this.model.schema.models.find((model) => model.name === name);
|
|
51
|
+
}
|
|
52
|
+
#parseHasMany() {
|
|
53
|
+
const selfSchema = this.#schemaForModel();
|
|
54
|
+
this.#hasMany = {};
|
|
55
|
+
selfSchema?.fields?.forEach((field) => {
|
|
56
|
+
if (field.isList) {
|
|
57
|
+
const otherSchema = this.#schemaForModel(field.type);
|
|
58
|
+
const belongsTo = otherSchema.fields.find(
|
|
59
|
+
(field2) => field2.type === this.model.name
|
|
60
|
+
);
|
|
61
|
+
this.#hasMany[field.name] = {
|
|
62
|
+
modelName: field.type,
|
|
63
|
+
referenceName: belongsTo.name,
|
|
64
|
+
// a null foreign key denotes an implicit many-to-many relationship
|
|
65
|
+
foreignKey: belongsTo.relationFromFields[0] || null,
|
|
66
|
+
primaryKey: belongsTo.relationToFields[0]
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
#parseBelongsTo() {
|
|
72
|
+
const selfSchema = this.#schemaForModel();
|
|
73
|
+
this.#belongsTo = {};
|
|
74
|
+
selfSchema?.fields?.forEach((field) => {
|
|
75
|
+
if (field.relationFromFields?.length) {
|
|
76
|
+
this.#belongsTo[field.name] = {
|
|
77
|
+
modelName: field.type,
|
|
78
|
+
foreignKey: field.relationFromFields[0],
|
|
79
|
+
primaryKey: field.relationToFields[0]
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
#parseAttributes() {
|
|
85
|
+
const selfSchema = this.#schemaForModel();
|
|
86
|
+
this.#attributes = {};
|
|
87
|
+
if (!this.#hasMany) {
|
|
88
|
+
this.#parseHasMany();
|
|
89
|
+
}
|
|
90
|
+
if (!this.belongsTo) {
|
|
91
|
+
this.#parseBelongsTo();
|
|
92
|
+
}
|
|
93
|
+
selfSchema?.fields?.forEach((field) => {
|
|
94
|
+
const { name, ...props } = field;
|
|
95
|
+
if (!Object.keys(this.#hasMany).includes(name) && !Object.keys(this.#belongsTo).includes(name)) {
|
|
96
|
+
this.#attributes[name] = props;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|