@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
|
@@ -0,0 +1,138 @@
|
|
|
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 RelationProxy_exports = {};
|
|
29
|
+
__export(RelationProxy_exports, {
|
|
30
|
+
default: () => RelationProxy
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(RelationProxy_exports);
|
|
33
|
+
var import_Reflection = __toESM(require("./Reflection"), 1);
|
|
34
|
+
class RelationProxy {
|
|
35
|
+
static addRelations(record) {
|
|
36
|
+
const reflection = new import_Reflection.default(record.constructor);
|
|
37
|
+
this.#addHasManyRelations(record, reflection.hasMany);
|
|
38
|
+
this.#addBelongsToRelations(record, reflection.belongsTo);
|
|
39
|
+
}
|
|
40
|
+
static #addHasManyRelations(record, hasMany) {
|
|
41
|
+
for (const [name, options] of Object.entries(hasMany)) {
|
|
42
|
+
if (record.hasOwnProperty(name)) {
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const model = record.constructor.requiredModels.find((requiredModel) => {
|
|
46
|
+
return requiredModel.name === options.modelName;
|
|
47
|
+
});
|
|
48
|
+
if (!model) {
|
|
49
|
+
console.warn(
|
|
50
|
+
`Model ${record.constructor.name} has a relationship defined for \`${name}\` in schema.prisma, but there is no Redwoood model for this relationship.`
|
|
51
|
+
);
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
Object.defineProperty(record, name, {
|
|
55
|
+
get() {
|
|
56
|
+
if (options.foreignKey === null) {
|
|
57
|
+
return new RelationProxy(model, {
|
|
58
|
+
where: {
|
|
59
|
+
[options.referenceName]: {
|
|
60
|
+
some: { [options.primaryKey]: record[options.primaryKey] }
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
create: {
|
|
64
|
+
[options.referenceName]: {
|
|
65
|
+
connect: [
|
|
66
|
+
{ [options.primaryKey]: record[options.primaryKey] }
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
} else {
|
|
72
|
+
return new RelationProxy(model, {
|
|
73
|
+
where: { [options.foreignKey]: record[options.primaryKey] }
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
enumerable: true
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
static #addBelongsToRelations(record, belongsTo) {
|
|
82
|
+
for (const [name, options] of Object.entries(belongsTo)) {
|
|
83
|
+
if (record.hasOwnProperty(name)) {
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const model = record.constructor.requiredModels.find((requiredModel) => {
|
|
87
|
+
return requiredModel.name === options.modelName;
|
|
88
|
+
});
|
|
89
|
+
if (!model) {
|
|
90
|
+
console.warn(
|
|
91
|
+
`Model ${record.constructor.name} has a relationship defined for \`${name}\` in schema.prisma, but there is no Redwoood model for this relationship.`
|
|
92
|
+
);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
Object.defineProperty(record, name, {
|
|
96
|
+
async get() {
|
|
97
|
+
return await model.findBy({
|
|
98
|
+
[options.primaryKey]: record[options.foreignKey]
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
enumerable: true
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
constructor(model, relation) {
|
|
106
|
+
this.model = model;
|
|
107
|
+
this.relation = relation;
|
|
108
|
+
}
|
|
109
|
+
all(...args) {
|
|
110
|
+
return this.where(...args);
|
|
111
|
+
}
|
|
112
|
+
create(attributes, options = {}) {
|
|
113
|
+
let relatedAttributes = { ...attributes };
|
|
114
|
+
if (this.relation.create) {
|
|
115
|
+
relatedAttributes = { ...relatedAttributes, ...this.relation.create };
|
|
116
|
+
} else {
|
|
117
|
+
relatedAttributes = { ...relatedAttributes, ...this.relation.where };
|
|
118
|
+
}
|
|
119
|
+
return this.model.create(relatedAttributes, options);
|
|
120
|
+
}
|
|
121
|
+
find(id, options = {}) {
|
|
122
|
+
return this.findBy({ [this.model.primaryKey]: id }, options);
|
|
123
|
+
}
|
|
124
|
+
findBy(attributes, options = {}) {
|
|
125
|
+
const relatedAttributes = {
|
|
126
|
+
...attributes,
|
|
127
|
+
...this.relation.where
|
|
128
|
+
};
|
|
129
|
+
return this.model.findBy(relatedAttributes, options);
|
|
130
|
+
}
|
|
131
|
+
first(...args) {
|
|
132
|
+
return this.findBy(...args);
|
|
133
|
+
}
|
|
134
|
+
where(attributes, options = {}) {
|
|
135
|
+
const relatedAttributes = { ...attributes, ...this.relation.where };
|
|
136
|
+
return this.model.where(relatedAttributes, options);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
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 ValidationMixin_exports = {};
|
|
19
|
+
__export(ValidationMixin_exports, {
|
|
20
|
+
default: () => ValidationMixin_default
|
|
21
|
+
});
|
|
22
|
+
module.exports = __toCommonJS(ValidationMixin_exports);
|
|
23
|
+
var import_api = require("@cedarjs/api");
|
|
24
|
+
var ValidationMixin_default = (Base) => class extends Base {
|
|
25
|
+
// Stores error messages internally
|
|
26
|
+
_errors = { base: [] };
|
|
27
|
+
// Removes all error messages.
|
|
28
|
+
_clearErrors() {
|
|
29
|
+
for (const [attribute, _array] of Object.entries(this._errors)) {
|
|
30
|
+
this._errors[attribute] = [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
// Denotes validations that need to run for the given fields. Must be in the
|
|
34
|
+
// form of { field: options } where `field` is the name of the field and
|
|
35
|
+
// `options` are the validation options. See Service Validations docs for
|
|
36
|
+
// usage examples: https://redwoodjs.com/docs/services.html#service-validations
|
|
37
|
+
//
|
|
38
|
+
// static validates = {
|
|
39
|
+
// emailAddress: { email: true },
|
|
40
|
+
// name: { presence: true, length: { min: 2, max: 255 } }
|
|
41
|
+
// }
|
|
42
|
+
static validates = {};
|
|
43
|
+
// Whether or not this instance is valid and has no errors. Essentially the
|
|
44
|
+
// opposite of `hasError`, but runs validations first. This means it will
|
|
45
|
+
// reset any custom errors added with `addError()`
|
|
46
|
+
get isValid() {
|
|
47
|
+
this.validate();
|
|
48
|
+
return !this.hasError;
|
|
49
|
+
}
|
|
50
|
+
get errors() {
|
|
51
|
+
return this._errors;
|
|
52
|
+
}
|
|
53
|
+
// Whether or not this instance contains any errors according to validation
|
|
54
|
+
// rules. Does not run valiations, (and so preserves custom errors) returns
|
|
55
|
+
// the state of error objects. Essentially the opposite of `isValid`.
|
|
56
|
+
get hasError() {
|
|
57
|
+
return !Object.entries(this._errors).every(
|
|
58
|
+
([_name, errors]) => !errors.length
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
// Adds an error to the _errors object. Can be called manually via instance,
|
|
62
|
+
// however any errors added this way will be wiped out if calling `validate()`
|
|
63
|
+
addError(attribute, message) {
|
|
64
|
+
if (!this._errors[attribute]) {
|
|
65
|
+
this._errors[attribute] = [];
|
|
66
|
+
}
|
|
67
|
+
this._errors[attribute].push(message);
|
|
68
|
+
}
|
|
69
|
+
// Checks each field against validate directives. Creates errors if so and
|
|
70
|
+
// returns `false`, otherwise returns `true`.
|
|
71
|
+
validate(options = {}) {
|
|
72
|
+
this._clearErrors();
|
|
73
|
+
if (this.constructor.validates.length === 0) {
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
const results = [];
|
|
77
|
+
for (const [name, recipe] of Object.entries(this.constructor.validates)) {
|
|
78
|
+
try {
|
|
79
|
+
(0, import_api.validate)(this[name], name, recipe);
|
|
80
|
+
results.push(true);
|
|
81
|
+
} catch (e) {
|
|
82
|
+
this.addError(name, e.message);
|
|
83
|
+
if (options.throw) {
|
|
84
|
+
throw e;
|
|
85
|
+
} else {
|
|
86
|
+
results.push(false);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return results.every((result) => result);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
@@ -0,0 +1,121 @@
|
|
|
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 parse_exports = {};
|
|
29
|
+
__export(parse_exports, {
|
|
30
|
+
parseDatamodel: () => parseDatamodel
|
|
31
|
+
});
|
|
32
|
+
module.exports = __toCommonJS(parse_exports);
|
|
33
|
+
var import_fs = __toESM(require("fs"), 1);
|
|
34
|
+
var import_path = __toESM(require("path"), 1);
|
|
35
|
+
var import_internals = require("@prisma/internals");
|
|
36
|
+
var esbuild = __toESM(require("esbuild"), 1);
|
|
37
|
+
var import_project_config = require("@cedarjs/project-config");
|
|
38
|
+
const DATAMODEL_PATH = import_path.default.join((0, import_project_config.getPaths)().api.models, "datamodel.js");
|
|
39
|
+
const MODELS_PATH = import_path.default.join((0, import_project_config.getPaths)().api.src, "models");
|
|
40
|
+
const MODELS_INDEX_PATH = import_path.default.join(MODELS_PATH, "index.js");
|
|
41
|
+
const indexLines = [
|
|
42
|
+
"// This file is autogenerated by Redwood and will be overwritten periodically",
|
|
43
|
+
"",
|
|
44
|
+
"import { db } from 'src/lib/db'",
|
|
45
|
+
"import datamodel from 'src/models/datamodel'",
|
|
46
|
+
"import { RedwoodRecord } from '@cedarjs/record'",
|
|
47
|
+
"",
|
|
48
|
+
"RedwoodRecord.db = db",
|
|
49
|
+
"RedwoodRecord.schema = datamodel",
|
|
50
|
+
""
|
|
51
|
+
];
|
|
52
|
+
const modelImports = [];
|
|
53
|
+
const modelRequires = {};
|
|
54
|
+
let datamodel;
|
|
55
|
+
const parseDatamodel = () => {
|
|
56
|
+
const schema = (0, import_internals.getSchema)((0, import_project_config.getPaths)().api.dbSchema);
|
|
57
|
+
(0, import_internals.getDMMF)({ datamodel: schema }).then((schema2) => {
|
|
58
|
+
datamodel = schema2.datamodel;
|
|
59
|
+
try {
|
|
60
|
+
const dir = import_path.default.dirname(DATAMODEL_PATH);
|
|
61
|
+
if (!import_fs.default.existsSync(dir)) {
|
|
62
|
+
import_fs.default.mkdirSync(dir, { recursive: true });
|
|
63
|
+
}
|
|
64
|
+
import_fs.default.writeFileSync(
|
|
65
|
+
DATAMODEL_PATH,
|
|
66
|
+
esbuild.transformSync(JSON.stringify(datamodel, null, 2), {
|
|
67
|
+
loader: "json",
|
|
68
|
+
format: "cjs"
|
|
69
|
+
}).code
|
|
70
|
+
);
|
|
71
|
+
console.info(`
|
|
72
|
+
Wrote ${DATAMODEL_PATH}`);
|
|
73
|
+
} catch (e) {
|
|
74
|
+
console.error("Error writing datamodel to", DATAMODEL_PATH);
|
|
75
|
+
}
|
|
76
|
+
const modelNames = import_fs.default.readdirSync(MODELS_PATH).map((file) => {
|
|
77
|
+
if (file !== "index.js" && file !== "datamodel.js") {
|
|
78
|
+
return file.split(".")[0];
|
|
79
|
+
}
|
|
80
|
+
}).filter((val) => val);
|
|
81
|
+
if (modelNames.length === 0) {
|
|
82
|
+
console.warn("No models found in", MODELS_PATH);
|
|
83
|
+
console.warn(
|
|
84
|
+
"Please create a model to represent the database table you want to access."
|
|
85
|
+
);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
modelNames.forEach((modelName) => {
|
|
89
|
+
const thisModelRequires = [];
|
|
90
|
+
modelImports.push(`import ${modelName} from 'src/models/${modelName}'`);
|
|
91
|
+
const schemaModel = datamodel.models.find(
|
|
92
|
+
(model) => model.name === modelName
|
|
93
|
+
);
|
|
94
|
+
if (schemaModel) {
|
|
95
|
+
schemaModel.fields.forEach((field) => {
|
|
96
|
+
if (field.kind === "object" && modelNames.includes(field.type)) {
|
|
97
|
+
thisModelRequires.push(field.type);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
modelRequires[modelName] = thisModelRequires;
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
modelImports.forEach((modelImport) => {
|
|
104
|
+
indexLines.push(modelImport);
|
|
105
|
+
});
|
|
106
|
+
indexLines.push("");
|
|
107
|
+
for (const [name, requires] of Object.entries(modelRequires)) {
|
|
108
|
+
indexLines.push(`${name}.requiredModels = [${requires.join(", ")}]`);
|
|
109
|
+
}
|
|
110
|
+
indexLines.push("");
|
|
111
|
+
indexLines.push(`export { ${modelNames.join(", ")} }`);
|
|
112
|
+
indexLines.push("");
|
|
113
|
+
import_fs.default.writeFileSync(MODELS_INDEX_PATH, indexLines.join("\n"));
|
|
114
|
+
console.info(` Wrote ${MODELS_INDEX_PATH}
|
|
115
|
+
`);
|
|
116
|
+
});
|
|
117
|
+
};
|
|
118
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
119
|
+
0 && (module.exports = {
|
|
120
|
+
parseDatamodel
|
|
121
|
+
});
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { RedwoodError } from "@cedarjs/api";
|
|
2
|
+
class RedwoodRecordError extends RedwoodError {
|
|
3
|
+
constructor() {
|
|
4
|
+
super();
|
|
5
|
+
this.name = "RedwoodRecordError";
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
class RedwoodRecordUncaughtError extends RedwoodError {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "RedwoodRecordUncaughtError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
class RedwoodRecordNotFoundError extends RedwoodError {
|
|
15
|
+
constructor(name) {
|
|
16
|
+
super(`${name} record not found`);
|
|
17
|
+
this.name = "RedwoodRecordNotFoundError";
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
class RedwoodRecordNullAttributeError extends RedwoodError {
|
|
21
|
+
constructor(name) {
|
|
22
|
+
super(`${name} must not be null`);
|
|
23
|
+
this.name = "RedwoodRecordNullAttributeError";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
class RedwoodRecordMissingAttributeError extends RedwoodError {
|
|
27
|
+
constructor(name) {
|
|
28
|
+
super(`${name} is missing`);
|
|
29
|
+
this.name = "RedwoodRecordMissingAttributeError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
class RedwoodRecordMissingRequiredModelError extends RedwoodError {
|
|
33
|
+
constructor(modelName, requiredModelName) {
|
|
34
|
+
super(
|
|
35
|
+
`Tried to build a relationship for ${requiredModelName} model but is not listed as a \`requiredModel\` in ${modelName}`
|
|
36
|
+
);
|
|
37
|
+
this.name = "RedwoodRecordMissingRequiredModelError";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
export {
|
|
41
|
+
RedwoodRecordError,
|
|
42
|
+
RedwoodRecordMissingAttributeError,
|
|
43
|
+
RedwoodRecordMissingRequiredModelError,
|
|
44
|
+
RedwoodRecordNotFoundError,
|
|
45
|
+
RedwoodRecordNullAttributeError,
|
|
46
|
+
RedwoodRecordUncaughtError
|
|
47
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export * from "./errors";
|
|
2
|
+
import { default as default2 } from "./redwoodrecord/Core";
|
|
3
|
+
import { default as default3 } from "./redwoodrecord/Reflection";
|
|
4
|
+
import { default as default4 } from "./redwoodrecord/RelationProxy";
|
|
5
|
+
import { default as default5 } from "./redwoodrecord/ValidationMixin";
|
|
6
|
+
import { default as default6 } from "./redwoodrecord/RedwoodRecord";
|
|
7
|
+
export * from "./tasks/parse";
|
|
8
|
+
export {
|
|
9
|
+
default2 as Core,
|
|
10
|
+
default6 as RedwoodRecord,
|
|
11
|
+
default3 as Reflection,
|
|
12
|
+
default4 as RelationProxy,
|
|
13
|
+
default5 as ValidationMixin
|
|
14
|
+
};
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import camelCase from "camelcase";
|
|
2
|
+
import * as Errors from "../errors";
|
|
3
|
+
class Core {
|
|
4
|
+
////////////////////////////
|
|
5
|
+
// Public class properties
|
|
6
|
+
////////////////////////////
|
|
7
|
+
// Set in child class to override DB accessor name. This is the name of the
|
|
8
|
+
// property you would call on an instance of Prisma Client in order the query
|
|
9
|
+
// a model in your schema. i.e. For the call `db.user` the accessorName is
|
|
10
|
+
// "user". Not setting this property will use the default camelCase version of
|
|
11
|
+
// the class name itself as the accessor.
|
|
12
|
+
//
|
|
13
|
+
// static accessorName = 'users'
|
|
14
|
+
static accessorName;
|
|
15
|
+
// Set in child class to override primary key field name for this model.
|
|
16
|
+
//
|
|
17
|
+
// static primaryKey = 'userId'
|
|
18
|
+
static primaryKey = "id";
|
|
19
|
+
// Parsed schema.prisma for use in subclasses
|
|
20
|
+
static schema = null;
|
|
21
|
+
// Any other model that may be required by this model
|
|
22
|
+
static requiredModels = [];
|
|
23
|
+
/////////////////////////
|
|
24
|
+
// Public class methods
|
|
25
|
+
/////////////////////////
|
|
26
|
+
// Access the raw Prisma Client for doing low level query manipulation
|
|
27
|
+
static db = null;
|
|
28
|
+
// Returns the Prisma DB accessor instance (ex. db.user)
|
|
29
|
+
static get accessor() {
|
|
30
|
+
return this.db[this.accessorName || camelCase(this.name)];
|
|
31
|
+
}
|
|
32
|
+
// Alias for where({})
|
|
33
|
+
static all(args) {
|
|
34
|
+
return this.where({}, args);
|
|
35
|
+
}
|
|
36
|
+
static build(attributes) {
|
|
37
|
+
const record = new this();
|
|
38
|
+
record.attributes = attributes;
|
|
39
|
+
return record;
|
|
40
|
+
}
|
|
41
|
+
// Create a new record. Instantiates a new instance and then calls .save() on it
|
|
42
|
+
static async create(attributes, options = {}) {
|
|
43
|
+
const record = this.build(attributes);
|
|
44
|
+
return await record.save(options);
|
|
45
|
+
}
|
|
46
|
+
// Find a single record by ID.
|
|
47
|
+
static async find(id, options = {}) {
|
|
48
|
+
const record = await this.findBy(
|
|
49
|
+
{
|
|
50
|
+
[this.primaryKey]: id,
|
|
51
|
+
...options.where || {}
|
|
52
|
+
},
|
|
53
|
+
options
|
|
54
|
+
);
|
|
55
|
+
if (record === null) {
|
|
56
|
+
throw new Errors.RedwoodRecordNotFoundError(this.name);
|
|
57
|
+
}
|
|
58
|
+
return record;
|
|
59
|
+
}
|
|
60
|
+
// Returns the first record matching the given `where`, otherwise first in the
|
|
61
|
+
// whole table (whatever the DB determines is the first record)
|
|
62
|
+
static async findBy(attributes, options = {}) {
|
|
63
|
+
const record = await this.accessor.findFirst({
|
|
64
|
+
where: attributes,
|
|
65
|
+
...options
|
|
66
|
+
});
|
|
67
|
+
return record ? await this.build(record) : null;
|
|
68
|
+
}
|
|
69
|
+
// Alias for findBy
|
|
70
|
+
static async first(...args) {
|
|
71
|
+
return this.findBy(...args);
|
|
72
|
+
}
|
|
73
|
+
// Find all records
|
|
74
|
+
static async where(attributes, options = {}) {
|
|
75
|
+
const records = await this.accessor.findMany({
|
|
76
|
+
where: attributes,
|
|
77
|
+
...options
|
|
78
|
+
});
|
|
79
|
+
return Promise.all(
|
|
80
|
+
records.map(async (record) => {
|
|
81
|
+
return await this.build(record);
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
///////////////////////////////
|
|
86
|
+
// Private instance properties
|
|
87
|
+
///////////////////////////////
|
|
88
|
+
// Stores instance attributes object internally
|
|
89
|
+
#attributes = {};
|
|
90
|
+
////////////////////////////
|
|
91
|
+
// Public instance methods
|
|
92
|
+
////////////////////////////
|
|
93
|
+
constructor() {
|
|
94
|
+
}
|
|
95
|
+
get attributes() {
|
|
96
|
+
return this.#attributes;
|
|
97
|
+
}
|
|
98
|
+
set attributes(attrs) {
|
|
99
|
+
if (attrs) {
|
|
100
|
+
this.#attributes = attrs;
|
|
101
|
+
this._createPropertiesForAttributes();
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
async destroy(options = {}) {
|
|
105
|
+
const { throw: shouldThrow, ...rest } = options;
|
|
106
|
+
try {
|
|
107
|
+
await this.constructor.accessor.delete({
|
|
108
|
+
where: { [this.constructor.primaryKey]: this.attributes.id },
|
|
109
|
+
...rest
|
|
110
|
+
});
|
|
111
|
+
return this;
|
|
112
|
+
} catch (e) {
|
|
113
|
+
this._deleteErrorHandler(e, shouldThrow);
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Saves the attributes to the database
|
|
118
|
+
async save(options = {}) {
|
|
119
|
+
const saveAttributes = JSON.parse(JSON.stringify(this.attributes));
|
|
120
|
+
try {
|
|
121
|
+
let newAttributes;
|
|
122
|
+
if (this.attributes[this.constructor.primaryKey]) {
|
|
123
|
+
delete saveAttributes[this.constructor.primaryKey];
|
|
124
|
+
newAttributes = await this.constructor.accessor.update({
|
|
125
|
+
where: {
|
|
126
|
+
[this.constructor.primaryKey]: this.attributes[this.constructor.primaryKey]
|
|
127
|
+
},
|
|
128
|
+
data: saveAttributes
|
|
129
|
+
});
|
|
130
|
+
} else {
|
|
131
|
+
newAttributes = await this.constructor.accessor.create({
|
|
132
|
+
data: saveAttributes
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
this.attributes = newAttributes;
|
|
136
|
+
} catch (e) {
|
|
137
|
+
this._saveErrorHandler(e, options.throw);
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
return this;
|
|
141
|
+
}
|
|
142
|
+
async update(attributes = {}, options = {}) {
|
|
143
|
+
this.#attributes = Object.assign(this.#attributes, attributes);
|
|
144
|
+
return await this.save(options);
|
|
145
|
+
}
|
|
146
|
+
////////////////////////////
|
|
147
|
+
// Private instance methods
|
|
148
|
+
////////////////////////////
|
|
149
|
+
// Turns a plain object's properties into getters/setters on the instance:
|
|
150
|
+
//
|
|
151
|
+
// const user = new User({ name: 'Rob' })
|
|
152
|
+
// user.name // => 'Rob'
|
|
153
|
+
_createPropertiesForAttributes() {
|
|
154
|
+
for (const [name, value] of Object.entries(this.attributes)) {
|
|
155
|
+
if (this.hasOwnProperty(name)) {
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (value && typeof value === "object" && (Object.keys(value).includes("create") || Object.keys(value).includes("connect"))) {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
this._createPropertyForAttribute(name);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
// Create property for a single attribute
|
|
165
|
+
_createPropertyForAttribute(name) {
|
|
166
|
+
Object.defineProperty(this, name, {
|
|
167
|
+
get() {
|
|
168
|
+
return this._attributeGetter(name);
|
|
169
|
+
},
|
|
170
|
+
set(value) {
|
|
171
|
+
this._attributeSetter(name, value);
|
|
172
|
+
},
|
|
173
|
+
enumerable: true
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
_attributeGetter(name) {
|
|
177
|
+
return this.#attributes[name];
|
|
178
|
+
}
|
|
179
|
+
_attributeSetter(name, value) {
|
|
180
|
+
return this.#attributes[name] = value;
|
|
181
|
+
}
|
|
182
|
+
_onSaveError(_name, _message) {
|
|
183
|
+
}
|
|
184
|
+
_deleteErrorHandler(error, shouldThrow) {
|
|
185
|
+
if (error.message.match(/Record to delete does not exist/)) {
|
|
186
|
+
this._onSaveError(
|
|
187
|
+
"base",
|
|
188
|
+
`${this.constructor.name} record to destroy not found`
|
|
189
|
+
);
|
|
190
|
+
if (shouldThrow) {
|
|
191
|
+
throw new Errors.RedwoodRecordNotFoundError();
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
this._onSaveError(
|
|
195
|
+
"base",
|
|
196
|
+
`${this.constructor.name} record threw uncaught error: ${error.message}`
|
|
197
|
+
);
|
|
198
|
+
if (shouldThrow) {
|
|
199
|
+
throw new Errors.RedwoodRecordUncaughtError(error.message);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Handles errors from saving a record (either update or create), converting
|
|
204
|
+
// to this.#errors messages, or throwing RedwoodRecord errors
|
|
205
|
+
_saveErrorHandler(error, shouldThrow) {
|
|
206
|
+
if (error.message.match(/Record to update not found/)) {
|
|
207
|
+
this._onSaveError(
|
|
208
|
+
"base",
|
|
209
|
+
`${this.constructor.name} record to update not found`
|
|
210
|
+
);
|
|
211
|
+
if (shouldThrow) {
|
|
212
|
+
throw new Errors.RedwoodRecordNotFoundError(this.constructor.name);
|
|
213
|
+
}
|
|
214
|
+
} else if (error.message.match(/must not be null/)) {
|
|
215
|
+
const [_all, name] = error.message.match(/Argument (\w+)/);
|
|
216
|
+
this._onSaveError(name, "must not be null");
|
|
217
|
+
if (shouldThrow) {
|
|
218
|
+
throw new Errors.RedwoodRecordNullAttributeError(name);
|
|
219
|
+
}
|
|
220
|
+
} else if (error.message.match(/is missing/)) {
|
|
221
|
+
const [_all, name] = error.message.match(/Argument (\w+)/);
|
|
222
|
+
this._onSaveError("base", `${name} is missing`);
|
|
223
|
+
if (shouldThrow) {
|
|
224
|
+
throw new Errors.RedwoodRecordMissingAttributeError(name);
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
this._onSaveError("base", error.message);
|
|
228
|
+
if (shouldThrow) {
|
|
229
|
+
throw new Errors.RedwoodRecordUncaughtError(error.message);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
export {
|
|
235
|
+
Core as default
|
|
236
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import Core from "./Core";
|
|
2
|
+
import Reflection from "./Reflection";
|
|
3
|
+
import RelationProxy from "./RelationProxy";
|
|
4
|
+
import ValidationMixin from "./ValidationMixin";
|
|
5
|
+
class RedwoodRecord extends ValidationMixin(Core) {
|
|
6
|
+
static get reflect() {
|
|
7
|
+
return new Reflection(this);
|
|
8
|
+
}
|
|
9
|
+
// Call original build, but add related attributes
|
|
10
|
+
static build(attributes) {
|
|
11
|
+
const record = super.build(attributes);
|
|
12
|
+
RelationProxy.addRelations(record, this.constructor.schema);
|
|
13
|
+
return record;
|
|
14
|
+
}
|
|
15
|
+
// Don't even try to save if data isn't valid
|
|
16
|
+
async save(options = {}) {
|
|
17
|
+
if (this.validate({ throw: options.throw })) {
|
|
18
|
+
return await super.save(options);
|
|
19
|
+
} else {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
// Call original method, but add error keys for validation
|
|
24
|
+
_createPropertyForAttribute(name) {
|
|
25
|
+
super._createPropertyForAttribute(name);
|
|
26
|
+
this._errors[name] = [];
|
|
27
|
+
}
|
|
28
|
+
// Add validation error on save error
|
|
29
|
+
_onSaveError(...args) {
|
|
30
|
+
super._onSaveError(...args);
|
|
31
|
+
this.addError(...args);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
RedwoodRecord as default
|
|
36
|
+
};
|