resora 0.1.0 → 0.1.2
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 +6 -0
- package/bin/index.mjs +189 -0
- package/dist/index.cjs +316 -1
- package/dist/index.d.cts +167 -3
- package/dist/index.d.mts +167 -3
- package/dist/index.mjs +283 -1
- package/package.json +10 -1
- package/stubs/resource.collection.stub +16 -0
- package/stubs/resource.stub +15 -0
package/README.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# Resora
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/resora)
|
|
4
|
+
[](https://www.npmjs.com/package/resora)
|
|
5
|
+
[](https://github.com/toneflix/resora/blob/main/LICENSE)
|
|
6
|
+
[](https://github.com/toneflix/resora/actions/workflows/ci.yml)
|
|
7
|
+
[](https://github.com/toneflix/resora/actions/workflows/deploy-docs.yml)
|
|
8
|
+
|
|
3
9
|
Resora is a structured API response layer for Node.js and TypeScript backends.
|
|
4
10
|
|
|
5
11
|
It provides a clean, explicit way to transform data into consistent JSON responses and automatically send them to the client. Resora supports single resources, collections, and pagination metadata while remaining framework-agnostic and strongly typed.
|
package/bin/index.mjs
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
#!/usr/bin/env ts-node
|
|
2
|
+
import path, { dirname, join } from "path";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
4
|
+
import { createRequire } from "module";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
import { Command, Kernel } from "@h3ravel/musket";
|
|
7
|
+
|
|
8
|
+
//#region src/utility.ts
|
|
9
|
+
const __dirname = /* @__PURE__ */ path.dirname(fileURLToPath(import.meta.url));
|
|
10
|
+
let stubsDir = path.resolve(__dirname, "../node_modules/resora/stubs");
|
|
11
|
+
if (!existsSync(stubsDir)) stubsDir = path.resolve(__dirname, "../stubs");
|
|
12
|
+
/**
|
|
13
|
+
* Define the configuration for the package
|
|
14
|
+
*
|
|
15
|
+
* @param userConfig The user configuration to override the default configuration
|
|
16
|
+
* @returns The merged configuration object
|
|
17
|
+
*/
|
|
18
|
+
const defineConfig = (userConfig = {}) => {
|
|
19
|
+
return Object.assign({
|
|
20
|
+
resourcesDir: "src/resources",
|
|
21
|
+
stubsDir,
|
|
22
|
+
stubs: {
|
|
23
|
+
resource: "resource.stub",
|
|
24
|
+
collection: "resource.collection.stub"
|
|
25
|
+
}
|
|
26
|
+
}, userConfig, { stubs: Object.assign({
|
|
27
|
+
resource: "resource.stub",
|
|
28
|
+
collection: "resource.collection.stub"
|
|
29
|
+
}, userConfig.stubs || {}) });
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/cli/actions.ts
|
|
34
|
+
var CliApp = class {
|
|
35
|
+
command;
|
|
36
|
+
config = {};
|
|
37
|
+
constructor(config = {}) {
|
|
38
|
+
this.config = defineConfig(config);
|
|
39
|
+
const require = createRequire(import.meta.url);
|
|
40
|
+
const possibleConfigPaths = [
|
|
41
|
+
join(process.cwd(), "resora.config.ts"),
|
|
42
|
+
join(process.cwd(), "resora.config.js"),
|
|
43
|
+
join(process.cwd(), "resora.config.cjs")
|
|
44
|
+
];
|
|
45
|
+
for (const configPath of possibleConfigPaths) if (existsSync(configPath)) try {
|
|
46
|
+
const { default: userConfig } = require(configPath);
|
|
47
|
+
Object.assign(this.config, defineConfig(userConfig));
|
|
48
|
+
break;
|
|
49
|
+
} catch (e) {
|
|
50
|
+
console.error(`Error loading config file at ${configPath}:`, e);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Utility to ensure directory exists
|
|
55
|
+
*
|
|
56
|
+
* @param filePath
|
|
57
|
+
*/
|
|
58
|
+
ensureDirectory(filePath) {
|
|
59
|
+
const dir = dirname(filePath);
|
|
60
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Utility to generate file from stub
|
|
64
|
+
*
|
|
65
|
+
* @param stubPath
|
|
66
|
+
* @param outputPath
|
|
67
|
+
* @param replacements
|
|
68
|
+
*/
|
|
69
|
+
generateFile(stubPath, outputPath, replacements, options) {
|
|
70
|
+
if (existsSync(outputPath) && !options?.force) {
|
|
71
|
+
this.command.error(`Error: ${outputPath} already exists.`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
} else if (existsSync(outputPath) && options?.force) rmSync(outputPath);
|
|
74
|
+
let content = readFileSync(stubPath, "utf-8");
|
|
75
|
+
for (const [key, value] of Object.entries(replacements)) content = content.replace(new RegExp(`{{${key}}}`, "g"), value);
|
|
76
|
+
this.ensureDirectory(outputPath);
|
|
77
|
+
writeFileSync(outputPath, content);
|
|
78
|
+
return outputPath;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Command to create a new resource or resource collection file
|
|
82
|
+
*
|
|
83
|
+
* @param name
|
|
84
|
+
* @param options
|
|
85
|
+
*/
|
|
86
|
+
makeResource(name, options) {
|
|
87
|
+
let resourceName = name;
|
|
88
|
+
if (options?.collection && !name.endsWith("Collection") && !name.endsWith("Resource")) resourceName += "Collection";
|
|
89
|
+
else if (!options?.collection && !name.endsWith("Resource") && !name.endsWith("Collection")) resourceName += "Resource";
|
|
90
|
+
const fileName = `${resourceName}.ts`;
|
|
91
|
+
const outputPath = join(this.config.resourcesDir, fileName);
|
|
92
|
+
const stubPath = join(this.config.stubsDir, options?.collection || name.endsWith("Collection") ? this.config.stubs.collection : this.config.stubs.resource);
|
|
93
|
+
if (!existsSync(stubPath)) {
|
|
94
|
+
this.command.error(`Error: Stub file ${stubPath} not found.`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
}
|
|
97
|
+
const collectsName = resourceName.replace(/(Resource|Collection)$/, "") + "Resource";
|
|
98
|
+
const collects = `/**
|
|
99
|
+
* The resource that this collection collects.
|
|
100
|
+
*/
|
|
101
|
+
collects = ${collectsName}
|
|
102
|
+
`;
|
|
103
|
+
const collectsImport = `import ${collectsName} from './${collectsName}'\n`;
|
|
104
|
+
const hasCollects = (!!options?.collection || name.endsWith("Collection")) && existsSync(join(this.config.resourcesDir, `${collectsName}.ts`));
|
|
105
|
+
const path = this.generateFile(stubPath, outputPath, {
|
|
106
|
+
ResourceName: resourceName,
|
|
107
|
+
CollectionResourceName: resourceName.replace(/(Resource|Collection)$/, "") + "Resource",
|
|
108
|
+
"collects = Resource": hasCollects ? collects : "",
|
|
109
|
+
"import = Resource": hasCollects ? collectsImport : ""
|
|
110
|
+
}, options);
|
|
111
|
+
return {
|
|
112
|
+
name: resourceName,
|
|
113
|
+
path
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
//#endregion
|
|
119
|
+
//#region src/cli/commands/MakeResource.ts
|
|
120
|
+
var MakeResource = class extends Command {
|
|
121
|
+
signature = `#create:
|
|
122
|
+
{resource : Generates a new resource file.
|
|
123
|
+
| {name : Name of the resource to create}
|
|
124
|
+
| {--c|collection : Make a resource collection}
|
|
125
|
+
| {--force : Create the resource or collection file even if it already exists.}
|
|
126
|
+
}
|
|
127
|
+
{collection : Create a new resource collection file.
|
|
128
|
+
| {name : Name of the resource collection to create}
|
|
129
|
+
| {--force : Create the resource or collection file even if it already exists.}
|
|
130
|
+
}
|
|
131
|
+
{all : Create both resource and collection files.
|
|
132
|
+
| {prefix : prefix of the resources to create, "Admin" will create AdminResource, AdminCollection}
|
|
133
|
+
| {--force : Create the resource or collection file even if it already exists.}
|
|
134
|
+
}
|
|
135
|
+
`;
|
|
136
|
+
description = "Create a new resource or resource collection file";
|
|
137
|
+
async handle() {
|
|
138
|
+
this.app.command = this;
|
|
139
|
+
let path = "";
|
|
140
|
+
const action = this.dictionary.name || this.dictionary.baseCommand;
|
|
141
|
+
if (["resource", "collection"].includes(action) && !this.argument("name")) return void this.error("Error: Name argument is required.");
|
|
142
|
+
if (action === "all" && !this.argument("prefix")) return void this.error("Error: Prefix argument is required.");
|
|
143
|
+
switch (action) {
|
|
144
|
+
case "resource":
|
|
145
|
+
({path} = this.app.makeResource(this.argument("name"), this.options()));
|
|
146
|
+
break;
|
|
147
|
+
case "collection":
|
|
148
|
+
({path} = this.app.makeResource(this.argument("name") + "Collection", this.options()));
|
|
149
|
+
break;
|
|
150
|
+
case "all": {
|
|
151
|
+
const o1 = this.app.makeResource(this.argument("prefix"), { force: this.option("force") });
|
|
152
|
+
const o2 = this.app.makeResource(this.argument("prefix") + "Collection", {
|
|
153
|
+
collection: true,
|
|
154
|
+
force: this.option("force")
|
|
155
|
+
});
|
|
156
|
+
path = `${o1.path}, ${o2.path}`;
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
default: this.fail(`Unknown action: ${action}`);
|
|
160
|
+
}
|
|
161
|
+
this.success(`Created: ${path}`);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
//#endregion
|
|
166
|
+
//#region src/cli/logo.ts
|
|
167
|
+
var logo_default = String.raw`
|
|
168
|
+
_____
|
|
169
|
+
| __ \
|
|
170
|
+
| |__) |___ ___ ___ _ __ __ _
|
|
171
|
+
| _ // _ \/ __|/ _ \| '__/ _, |
|
|
172
|
+
| | \ \ __/\__ \ (_) | | | (_| |
|
|
173
|
+
|_| \_\___||___/\___/|_| \__,_|
|
|
174
|
+
`;
|
|
175
|
+
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/cli/index.ts
|
|
178
|
+
const app = new CliApp();
|
|
179
|
+
await Kernel.init(app, {
|
|
180
|
+
logo: logo_default,
|
|
181
|
+
name: "Resora CLI",
|
|
182
|
+
baseCommands: [MakeResource],
|
|
183
|
+
exceptionHandler(exception) {
|
|
184
|
+
throw exception;
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
//#endregion
|
|
189
|
+
export { };
|
package/dist/index.cjs
CHANGED
|
@@ -1,4 +1,37 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __copyProps = (to, from, except, desc) => {
|
|
10
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
14
|
+
__defProp(to, key, {
|
|
15
|
+
get: ((k) => from[k]).bind(null, key),
|
|
16
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return to;
|
|
22
|
+
};
|
|
23
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
24
|
+
value: mod,
|
|
25
|
+
enumerable: true
|
|
26
|
+
}) : target, mod));
|
|
27
|
+
|
|
28
|
+
//#endregion
|
|
29
|
+
let path = require("path");
|
|
30
|
+
path = __toESM(path);
|
|
31
|
+
let fs = require("fs");
|
|
32
|
+
let module$1 = require("module");
|
|
33
|
+
let url = require("url");
|
|
34
|
+
let _h3ravel_musket = require("@h3ravel/musket");
|
|
2
35
|
|
|
3
36
|
//#region src/ApiResource.ts
|
|
4
37
|
/**
|
|
@@ -11,6 +44,175 @@ function ApiResource(instance) {
|
|
|
11
44
|
return instance;
|
|
12
45
|
}
|
|
13
46
|
|
|
47
|
+
//#endregion
|
|
48
|
+
//#region src/utility.ts
|
|
49
|
+
const __dirname$1 = /* @__PURE__ */ path.default.dirname((0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
|
|
50
|
+
let stubsDir = path.default.resolve(__dirname$1, "../node_modules/resora/stubs");
|
|
51
|
+
if (!(0, fs.existsSync)(stubsDir)) stubsDir = path.default.resolve(__dirname$1, "../stubs");
|
|
52
|
+
/**
|
|
53
|
+
* Define the configuration for the package
|
|
54
|
+
*
|
|
55
|
+
* @param userConfig The user configuration to override the default configuration
|
|
56
|
+
* @returns The merged configuration object
|
|
57
|
+
*/
|
|
58
|
+
const defineConfig = (userConfig = {}) => {
|
|
59
|
+
return Object.assign({
|
|
60
|
+
resourcesDir: "src/resources",
|
|
61
|
+
stubsDir,
|
|
62
|
+
stubs: {
|
|
63
|
+
resource: "resource.stub",
|
|
64
|
+
collection: "resource.collection.stub"
|
|
65
|
+
}
|
|
66
|
+
}, userConfig, { stubs: Object.assign({
|
|
67
|
+
resource: "resource.stub",
|
|
68
|
+
collection: "resource.collection.stub"
|
|
69
|
+
}, userConfig.stubs || {}) });
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region src/cli/actions.ts
|
|
74
|
+
var CliApp = class {
|
|
75
|
+
command;
|
|
76
|
+
config = {};
|
|
77
|
+
constructor(config = {}) {
|
|
78
|
+
this.config = defineConfig(config);
|
|
79
|
+
const require = (0, module$1.createRequire)(require("url").pathToFileURL(__filename).href);
|
|
80
|
+
const possibleConfigPaths = [
|
|
81
|
+
(0, path.join)(process.cwd(), "resora.config.ts"),
|
|
82
|
+
(0, path.join)(process.cwd(), "resora.config.js"),
|
|
83
|
+
(0, path.join)(process.cwd(), "resora.config.cjs")
|
|
84
|
+
];
|
|
85
|
+
for (const configPath of possibleConfigPaths) if ((0, fs.existsSync)(configPath)) try {
|
|
86
|
+
const { default: userConfig } = require(configPath);
|
|
87
|
+
Object.assign(this.config, defineConfig(userConfig));
|
|
88
|
+
break;
|
|
89
|
+
} catch (e) {
|
|
90
|
+
console.error(`Error loading config file at ${configPath}:`, e);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Utility to ensure directory exists
|
|
95
|
+
*
|
|
96
|
+
* @param filePath
|
|
97
|
+
*/
|
|
98
|
+
ensureDirectory(filePath) {
|
|
99
|
+
const dir = (0, path.dirname)(filePath);
|
|
100
|
+
if (!(0, fs.existsSync)(dir)) (0, fs.mkdirSync)(dir, { recursive: true });
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Utility to generate file from stub
|
|
104
|
+
*
|
|
105
|
+
* @param stubPath
|
|
106
|
+
* @param outputPath
|
|
107
|
+
* @param replacements
|
|
108
|
+
*/
|
|
109
|
+
generateFile(stubPath, outputPath, replacements, options) {
|
|
110
|
+
if ((0, fs.existsSync)(outputPath) && !options?.force) {
|
|
111
|
+
this.command.error(`Error: ${outputPath} already exists.`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
} else if ((0, fs.existsSync)(outputPath) && options?.force) (0, fs.rmSync)(outputPath);
|
|
114
|
+
let content = (0, fs.readFileSync)(stubPath, "utf-8");
|
|
115
|
+
for (const [key, value] of Object.entries(replacements)) content = content.replace(new RegExp(`{{${key}}}`, "g"), value);
|
|
116
|
+
this.ensureDirectory(outputPath);
|
|
117
|
+
(0, fs.writeFileSync)(outputPath, content);
|
|
118
|
+
return outputPath;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Command to create a new resource or resource collection file
|
|
122
|
+
*
|
|
123
|
+
* @param name
|
|
124
|
+
* @param options
|
|
125
|
+
*/
|
|
126
|
+
makeResource(name, options) {
|
|
127
|
+
let resourceName = name;
|
|
128
|
+
if (options?.collection && !name.endsWith("Collection") && !name.endsWith("Resource")) resourceName += "Collection";
|
|
129
|
+
else if (!options?.collection && !name.endsWith("Resource") && !name.endsWith("Collection")) resourceName += "Resource";
|
|
130
|
+
const fileName = `${resourceName}.ts`;
|
|
131
|
+
const outputPath = (0, path.join)(this.config.resourcesDir, fileName);
|
|
132
|
+
const stubPath = (0, path.join)(this.config.stubsDir, options?.collection || name.endsWith("Collection") ? this.config.stubs.collection : this.config.stubs.resource);
|
|
133
|
+
if (!(0, fs.existsSync)(stubPath)) {
|
|
134
|
+
this.command.error(`Error: Stub file ${stubPath} not found.`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
const collectsName = resourceName.replace(/(Resource|Collection)$/, "") + "Resource";
|
|
138
|
+
const collects = `/**
|
|
139
|
+
* The resource that this collection collects.
|
|
140
|
+
*/
|
|
141
|
+
collects = ${collectsName}
|
|
142
|
+
`;
|
|
143
|
+
const collectsImport = `import ${collectsName} from './${collectsName}'\n`;
|
|
144
|
+
const hasCollects = (!!options?.collection || name.endsWith("Collection")) && (0, fs.existsSync)((0, path.join)(this.config.resourcesDir, `${collectsName}.ts`));
|
|
145
|
+
const path$2 = this.generateFile(stubPath, outputPath, {
|
|
146
|
+
ResourceName: resourceName,
|
|
147
|
+
CollectionResourceName: resourceName.replace(/(Resource|Collection)$/, "") + "Resource",
|
|
148
|
+
"collects = Resource": hasCollects ? collects : "",
|
|
149
|
+
"import = Resource": hasCollects ? collectsImport : ""
|
|
150
|
+
}, options);
|
|
151
|
+
return {
|
|
152
|
+
name: resourceName,
|
|
153
|
+
path: path$2
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
//#endregion
|
|
159
|
+
//#region src/cli/commands/MakeResource.ts
|
|
160
|
+
var MakeResource = class extends _h3ravel_musket.Command {
|
|
161
|
+
signature = `#create:
|
|
162
|
+
{resource : Generates a new resource file.
|
|
163
|
+
| {name : Name of the resource to create}
|
|
164
|
+
| {--c|collection : Make a resource collection}
|
|
165
|
+
| {--force : Create the resource or collection file even if it already exists.}
|
|
166
|
+
}
|
|
167
|
+
{collection : Create a new resource collection file.
|
|
168
|
+
| {name : Name of the resource collection to create}
|
|
169
|
+
| {--force : Create the resource or collection file even if it already exists.}
|
|
170
|
+
}
|
|
171
|
+
{all : Create both resource and collection files.
|
|
172
|
+
| {prefix : prefix of the resources to create, "Admin" will create AdminResource, AdminCollection}
|
|
173
|
+
| {--force : Create the resource or collection file even if it already exists.}
|
|
174
|
+
}
|
|
175
|
+
`;
|
|
176
|
+
description = "Create a new resource or resource collection file";
|
|
177
|
+
async handle() {
|
|
178
|
+
this.app.command = this;
|
|
179
|
+
let path = "";
|
|
180
|
+
const action = this.dictionary.name || this.dictionary.baseCommand;
|
|
181
|
+
if (["resource", "collection"].includes(action) && !this.argument("name")) return void this.error("Error: Name argument is required.");
|
|
182
|
+
if (action === "all" && !this.argument("prefix")) return void this.error("Error: Prefix argument is required.");
|
|
183
|
+
switch (action) {
|
|
184
|
+
case "resource":
|
|
185
|
+
({path} = this.app.makeResource(this.argument("name"), this.options()));
|
|
186
|
+
break;
|
|
187
|
+
case "collection":
|
|
188
|
+
({path} = this.app.makeResource(this.argument("name") + "Collection", this.options()));
|
|
189
|
+
break;
|
|
190
|
+
case "all": {
|
|
191
|
+
const o1 = this.app.makeResource(this.argument("prefix"), { force: this.option("force") });
|
|
192
|
+
const o2 = this.app.makeResource(this.argument("prefix") + "Collection", {
|
|
193
|
+
collection: true,
|
|
194
|
+
force: this.option("force")
|
|
195
|
+
});
|
|
196
|
+
path = `${o1.path}, ${o2.path}`;
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
default: this.fail(`Unknown action: ${action}`);
|
|
200
|
+
}
|
|
201
|
+
this.success(`Created: ${path}`);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
//#endregion
|
|
206
|
+
//#region src/cli/logo.ts
|
|
207
|
+
var logo_default = String.raw`
|
|
208
|
+
_____
|
|
209
|
+
| __ \
|
|
210
|
+
| |__) |___ ___ ___ _ __ __ _
|
|
211
|
+
| _ // _ \/ __|/ _ \| '__/ _, |
|
|
212
|
+
| | \ \ __/\__ \ (_) | | | (_| |
|
|
213
|
+
|_| \_\___||___/\___/|_| \__,_|
|
|
214
|
+
`;
|
|
215
|
+
|
|
14
216
|
//#endregion
|
|
15
217
|
//#region src/ServerResponse.ts
|
|
16
218
|
var ServerResponse = class {
|
|
@@ -125,6 +327,113 @@ var ServerResponse = class {
|
|
|
125
327
|
}
|
|
126
328
|
};
|
|
127
329
|
|
|
330
|
+
//#endregion
|
|
331
|
+
//#region src/GenericResource.ts
|
|
332
|
+
/**
|
|
333
|
+
* GenericResource class to handle API resource transformation and response building
|
|
334
|
+
*/
|
|
335
|
+
var GenericResource = class {
|
|
336
|
+
body = { data: {} };
|
|
337
|
+
resource;
|
|
338
|
+
collects;
|
|
339
|
+
called = {};
|
|
340
|
+
constructor(rsc, res) {
|
|
341
|
+
this.res = res;
|
|
342
|
+
this.resource = rsc;
|
|
343
|
+
/**
|
|
344
|
+
* Copy properties from rsc to this instance for easy
|
|
345
|
+
* access, but only if data is not an array
|
|
346
|
+
*/
|
|
347
|
+
if (!Array.isArray(this.resource.data ?? this.resource)) {
|
|
348
|
+
for (const key of Object.keys(this.resource.data ?? this.resource)) if (!(key in this)) Object.defineProperty(this, key, {
|
|
349
|
+
enumerable: true,
|
|
350
|
+
configurable: true,
|
|
351
|
+
get: () => {
|
|
352
|
+
return this.resource.data?.[key] ?? this.resource[key];
|
|
353
|
+
},
|
|
354
|
+
set: (value) => {
|
|
355
|
+
if (this.resource.data && this.resource.data[key]) this.resource.data[key] = value;
|
|
356
|
+
else this.resource[key] = value;
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Get the original resource data
|
|
363
|
+
*/
|
|
364
|
+
data() {
|
|
365
|
+
return this.resource;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Convert resource to JSON response format
|
|
369
|
+
*
|
|
370
|
+
* @returns
|
|
371
|
+
*/
|
|
372
|
+
json() {
|
|
373
|
+
if (!this.called.json) {
|
|
374
|
+
this.called.json = true;
|
|
375
|
+
const resource = this.data();
|
|
376
|
+
let data = Array.isArray(resource) ? [...resource] : { ...resource };
|
|
377
|
+
if (Array.isArray(data) && this.collects) {
|
|
378
|
+
data = data.map((item) => new this.collects(item).data());
|
|
379
|
+
this.resource = data;
|
|
380
|
+
}
|
|
381
|
+
if (typeof data.data !== "undefined") data = data.data;
|
|
382
|
+
if (this.resource.pagination && data.data && Array.isArray(data.data)) delete data.pagination;
|
|
383
|
+
this.body = { data };
|
|
384
|
+
if (Array.isArray(this.body.data) && this.resource.pagination) this.body.meta = { pagination: this.resource.pagination };
|
|
385
|
+
}
|
|
386
|
+
return this;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Convert resource to array format (for collections)
|
|
390
|
+
*
|
|
391
|
+
* @returns
|
|
392
|
+
*/
|
|
393
|
+
toArray() {
|
|
394
|
+
this.called.toArray = true;
|
|
395
|
+
this.json();
|
|
396
|
+
let data = Array.isArray(this.resource) ? [...this.resource] : { ...this.resource };
|
|
397
|
+
if (typeof data.data !== "undefined") data = data.data;
|
|
398
|
+
return data;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Add additional properties to the response body
|
|
402
|
+
*
|
|
403
|
+
* @param extra Additional properties to merge into the response body
|
|
404
|
+
* @returns
|
|
405
|
+
*/
|
|
406
|
+
additional(extra) {
|
|
407
|
+
this.called.additional = true;
|
|
408
|
+
this.json();
|
|
409
|
+
delete extra.data;
|
|
410
|
+
delete extra.pagination;
|
|
411
|
+
this.body = {
|
|
412
|
+
...this.body,
|
|
413
|
+
...extra
|
|
414
|
+
};
|
|
415
|
+
return this;
|
|
416
|
+
}
|
|
417
|
+
response(res) {
|
|
418
|
+
this.called.toResponse = true;
|
|
419
|
+
return new ServerResponse(res ?? this.res, this.body);
|
|
420
|
+
}
|
|
421
|
+
/**
|
|
422
|
+
* Promise-like then method to allow chaining with async/await or .then() syntax
|
|
423
|
+
*
|
|
424
|
+
* @param onfulfilled Callback to handle the fulfilled state of the promise, receiving the response body
|
|
425
|
+
* @param onrejected Callback to handle the rejected state of the promise, receiving the error reason
|
|
426
|
+
* @returns A promise that resolves to the result of the onfulfilled or onrejected callback
|
|
427
|
+
*/
|
|
428
|
+
then(onfulfilled, onrejected) {
|
|
429
|
+
this.called.then = true;
|
|
430
|
+
this.json();
|
|
431
|
+
const resolved = Promise.resolve(this.body).then(onfulfilled, onrejected);
|
|
432
|
+
if (this.res) this.res.send(this.body);
|
|
433
|
+
return resolved;
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
|
|
128
437
|
//#endregion
|
|
129
438
|
//#region src/ResourceCollection.ts
|
|
130
439
|
/**
|
|
@@ -368,4 +677,10 @@ var Resource = class {
|
|
|
368
677
|
|
|
369
678
|
//#endregion
|
|
370
679
|
exports.ApiResource = ApiResource;
|
|
371
|
-
exports.
|
|
680
|
+
exports.CliApp = CliApp;
|
|
681
|
+
exports.GenericResource = GenericResource;
|
|
682
|
+
exports.MakeResource = MakeResource;
|
|
683
|
+
exports.Resource = Resource;
|
|
684
|
+
exports.ResourceCollection = ResourceCollection;
|
|
685
|
+
exports.ServerResponse = ServerResponse;
|
|
686
|
+
exports.defineConfig = defineConfig;
|
package/dist/index.d.cts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Command } from "@h3ravel/musket";
|
|
1
2
|
import { H3Event } from "h3";
|
|
2
3
|
import { Response } from "express";
|
|
3
4
|
|
|
@@ -17,7 +18,7 @@ interface MetaData {
|
|
|
17
18
|
interface ResourceData {
|
|
18
19
|
[key: string]: any;
|
|
19
20
|
}
|
|
20
|
-
interface
|
|
21
|
+
interface ResourceDef extends ResourceData {
|
|
21
22
|
cursor?: Cursor | undefined;
|
|
22
23
|
pagination?: Pagination | undefined;
|
|
23
24
|
}
|
|
@@ -29,11 +30,38 @@ interface Collectible {
|
|
|
29
30
|
cursor?: Cursor | undefined;
|
|
30
31
|
pagination?: Pagination | undefined;
|
|
31
32
|
}
|
|
32
|
-
interface ResponseData<R extends ResourceData = any> extends
|
|
33
|
+
interface ResponseData<R extends ResourceData = any> extends ResourceDef {
|
|
33
34
|
data: R;
|
|
34
35
|
meta?: MetaData | undefined;
|
|
35
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* @description A type that represents the metadata for a paginated collection of resources. It extends the MetaData type and includes all properties of a Collectible object except for the data property. This type is used to provide additional information about the paginated collection, such as pagination details, without including the actual resource data.
|
|
39
|
+
* @example
|
|
40
|
+
* const paginatedMeta: PaginatedMetaData = {
|
|
41
|
+
* pagination: {
|
|
42
|
+
* currentPage: 1,
|
|
43
|
+
* total: 100
|
|
44
|
+
* },
|
|
45
|
+
* timestamp: '2024-06-01T12:00:00Z'
|
|
46
|
+
* };
|
|
47
|
+
*/
|
|
36
48
|
type PaginatedMetaData<R extends Collectible = Collectible> = MetaData & Omit<R, 'data'>;
|
|
49
|
+
/**
|
|
50
|
+
* @description A type that represents the body of a response for a collection of resources. It includes a data property, which can be either an array of ResourceData objects or a Collectible object, and an optional meta property that can contain any additional metadata related to the response. The type is generic and can be used to define the structure of the response body for API endpoints that return collections of resources.
|
|
51
|
+
* @example
|
|
52
|
+
* const collectionResponse: CollectionBody = {
|
|
53
|
+
* data: [
|
|
54
|
+
* { id: 1, name: 'Resource 1' },
|
|
55
|
+
* { id: 2, name: 'Resource 2' }
|
|
56
|
+
* ],
|
|
57
|
+
* meta: {
|
|
58
|
+
* pagination: {
|
|
59
|
+
* currentPage: 1,
|
|
60
|
+
* total: 2
|
|
61
|
+
* }
|
|
62
|
+
* }
|
|
63
|
+
* };
|
|
64
|
+
*/
|
|
37
65
|
interface ResponseDataCollection<R extends Collectible | undefined = undefined> extends ResourceData {
|
|
38
66
|
data: R extends Collectible ? R['data'] : R;
|
|
39
67
|
meta?: R extends Collectible ? PaginatedMetaData<R> | undefined : undefined;
|
|
@@ -80,6 +108,23 @@ type CollectionBody<R extends ResourceData[] | Collectible = ResourceData[]> = R
|
|
|
80
108
|
type ResourceBody<R extends ResourceData | NonCollectible = ResourceData> = ResponseData<R extends NonCollectible ? R : {
|
|
81
109
|
data: R;
|
|
82
110
|
}>;
|
|
111
|
+
/**
|
|
112
|
+
* @description A type that represents the body of a response for either a single resource or a collection of resources.
|
|
113
|
+
* It can be either a ResourceData object, an array of ResourceData objects, a NonCollectible object, or a Collectible object.
|
|
114
|
+
* The type also includes the meta property, which is optional and can contain any additional metadata related to the response.
|
|
115
|
+
* @example
|
|
116
|
+
* const genericResponse: GenericBody = {
|
|
117
|
+
* data: {
|
|
118
|
+
* id: 1,
|
|
119
|
+
* name: 'Resource Name',
|
|
120
|
+
* description: 'Resource Description'
|
|
121
|
+
* },
|
|
122
|
+
* meta: {
|
|
123
|
+
* timestamp: '2024-06-01T12:00:00Z'
|
|
124
|
+
* }
|
|
125
|
+
* };
|
|
126
|
+
*/
|
|
127
|
+
type GenericBody<R extends NonCollectible | Collectible | ResourceData = ResourceData> = ResponseData<R>;
|
|
83
128
|
/**
|
|
84
129
|
* @description A type that represents the pagination information for a collection of resources. It includes properties such as currentPage, from, to, perPage, total, firstPage, lastPage, prevPage, and nextPage. All properties are optional and can be undefined if not applicable.
|
|
85
130
|
* @example
|
|
@@ -118,6 +163,67 @@ interface Cursor {
|
|
|
118
163
|
previous?: string | undefined;
|
|
119
164
|
next?: string | undefined;
|
|
120
165
|
}
|
|
166
|
+
interface Config {
|
|
167
|
+
/**
|
|
168
|
+
* @description The directory where resource files are stored. This is the location where the generated resource files will be saved. It should be a valid path on the file system.
|
|
169
|
+
*/
|
|
170
|
+
resourcesDir: string;
|
|
171
|
+
/**
|
|
172
|
+
* @description The directory where stub files are stored. Stub files are templates used for generating resource files. This should also be a valid path on the file system where the stub templates are located.
|
|
173
|
+
*/
|
|
174
|
+
stubsDir: string;
|
|
175
|
+
/**
|
|
176
|
+
* @description An object that defines the stub file names for different types of resources.
|
|
177
|
+
*/
|
|
178
|
+
stubs: {
|
|
179
|
+
/**
|
|
180
|
+
* @description The stub file name for a resource. This stub will be used when generating a resource file.
|
|
181
|
+
*/
|
|
182
|
+
resource: string;
|
|
183
|
+
/**
|
|
184
|
+
* @description The stub file name for a collection resource. This stub will be used when generating a collection resource file.
|
|
185
|
+
*/
|
|
186
|
+
collection: string;
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
//#endregion
|
|
190
|
+
//#region src/cli/actions.d.ts
|
|
191
|
+
declare class CliApp {
|
|
192
|
+
command: Command;
|
|
193
|
+
private config;
|
|
194
|
+
constructor(config?: Partial<Config>);
|
|
195
|
+
/**
|
|
196
|
+
* Utility to ensure directory exists
|
|
197
|
+
*
|
|
198
|
+
* @param filePath
|
|
199
|
+
*/
|
|
200
|
+
ensureDirectory(filePath: string): void;
|
|
201
|
+
/**
|
|
202
|
+
* Utility to generate file from stub
|
|
203
|
+
*
|
|
204
|
+
* @param stubPath
|
|
205
|
+
* @param outputPath
|
|
206
|
+
* @param replacements
|
|
207
|
+
*/
|
|
208
|
+
generateFile(stubPath: string, outputPath: string, replacements: Record<string, string>, options?: any): string;
|
|
209
|
+
/**
|
|
210
|
+
* Command to create a new resource or resource collection file
|
|
211
|
+
*
|
|
212
|
+
* @param name
|
|
213
|
+
* @param options
|
|
214
|
+
*/
|
|
215
|
+
makeResource(name: string, options: any): {
|
|
216
|
+
name: string;
|
|
217
|
+
path: string;
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
//#endregion
|
|
221
|
+
//#region src/cli/commands/MakeResource.d.ts
|
|
222
|
+
declare class MakeResource extends Command<CliApp> {
|
|
223
|
+
protected signature: string;
|
|
224
|
+
protected description: string;
|
|
225
|
+
handle(): Promise<undefined>;
|
|
226
|
+
}
|
|
121
227
|
//#endregion
|
|
122
228
|
//#region src/ServerResponse.d.ts
|
|
123
229
|
declare class ServerResponse<R extends NonCollectible | Collectible | ResourceData[] | ResourceData = ResourceData> {
|
|
@@ -325,4 +431,62 @@ declare class Resource<R extends ResourceData | NonCollectible = ResourceData> {
|
|
|
325
431
|
finally(onfinally?: (() => void) | null): Promise<void>;
|
|
326
432
|
}
|
|
327
433
|
//#endregion
|
|
328
|
-
|
|
434
|
+
//#region src/GenericResource.d.ts
|
|
435
|
+
/**
|
|
436
|
+
* GenericResource class to handle API resource transformation and response building
|
|
437
|
+
*/
|
|
438
|
+
declare class GenericResource<R extends NonCollectible | Collectible | ResourceData = ResourceData, T extends ResourceData = any> {
|
|
439
|
+
private res?;
|
|
440
|
+
[key: string]: any;
|
|
441
|
+
body: GenericBody<R>;
|
|
442
|
+
resource: R;
|
|
443
|
+
collects?: typeof Resource<T>;
|
|
444
|
+
private called;
|
|
445
|
+
constructor(rsc: R, res?: Response | undefined);
|
|
446
|
+
/**
|
|
447
|
+
* Get the original resource data
|
|
448
|
+
*/
|
|
449
|
+
data(): R;
|
|
450
|
+
/**
|
|
451
|
+
* Convert resource to JSON response format
|
|
452
|
+
*
|
|
453
|
+
* @returns
|
|
454
|
+
*/
|
|
455
|
+
json(): this;
|
|
456
|
+
/**
|
|
457
|
+
* Convert resource to array format (for collections)
|
|
458
|
+
*
|
|
459
|
+
* @returns
|
|
460
|
+
*/
|
|
461
|
+
toArray(): any;
|
|
462
|
+
/**
|
|
463
|
+
* Add additional properties to the response body
|
|
464
|
+
*
|
|
465
|
+
* @param extra Additional properties to merge into the response body
|
|
466
|
+
* @returns
|
|
467
|
+
*/
|
|
468
|
+
additional<X extends Record<string, any>>(extra: X): this;
|
|
469
|
+
response(): ServerResponse<GenericBody<R>>;
|
|
470
|
+
response(res: H3Event['res']): ServerResponse<GenericBody<R>>;
|
|
471
|
+
/**
|
|
472
|
+
* Promise-like then method to allow chaining with async/await or .then() syntax
|
|
473
|
+
*
|
|
474
|
+
* @param onfulfilled Callback to handle the fulfilled state of the promise, receiving the response body
|
|
475
|
+
* @param onrejected Callback to handle the rejected state of the promise, receiving the error reason
|
|
476
|
+
* @returns A promise that resolves to the result of the onfulfilled or onrejected callback
|
|
477
|
+
*/
|
|
478
|
+
then<TResult1 = GenericBody<R>, TResult2 = never>(onfulfilled?: ((value: GenericBody<R>) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
479
|
+
}
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/utility.d.ts
|
|
482
|
+
/**
|
|
483
|
+
* Define the configuration for the package
|
|
484
|
+
*
|
|
485
|
+
* @param userConfig The user configuration to override the default configuration
|
|
486
|
+
* @returns The merged configuration object
|
|
487
|
+
*/
|
|
488
|
+
declare const defineConfig: (userConfig?: Partial<Omit<Config, "stubs">> & {
|
|
489
|
+
stubs?: Partial<Config["stubs"]>;
|
|
490
|
+
}) => Config;
|
|
491
|
+
//#endregion
|
|
492
|
+
export { ApiResource, CliApp, Collectible, CollectionBody, Config, Cursor, GenericBody, GenericResource, MakeResource, MetaData, NonCollectible, PaginatedMetaData, Pagination, Resource, ResourceBody, ResourceCollection, ResourceData, ResourceDef, ResponseData, ResponseDataCollection, ServerResponse, defineConfig };
|
package/dist/index.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Command } from "@h3ravel/musket";
|
|
1
2
|
import { H3Event } from "h3";
|
|
2
3
|
import { Response } from "express";
|
|
3
4
|
|
|
@@ -17,7 +18,7 @@ interface MetaData {
|
|
|
17
18
|
interface ResourceData {
|
|
18
19
|
[key: string]: any;
|
|
19
20
|
}
|
|
20
|
-
interface
|
|
21
|
+
interface ResourceDef extends ResourceData {
|
|
21
22
|
cursor?: Cursor | undefined;
|
|
22
23
|
pagination?: Pagination | undefined;
|
|
23
24
|
}
|
|
@@ -29,11 +30,38 @@ interface Collectible {
|
|
|
29
30
|
cursor?: Cursor | undefined;
|
|
30
31
|
pagination?: Pagination | undefined;
|
|
31
32
|
}
|
|
32
|
-
interface ResponseData<R extends ResourceData = any> extends
|
|
33
|
+
interface ResponseData<R extends ResourceData = any> extends ResourceDef {
|
|
33
34
|
data: R;
|
|
34
35
|
meta?: MetaData | undefined;
|
|
35
36
|
}
|
|
37
|
+
/**
|
|
38
|
+
* @description A type that represents the metadata for a paginated collection of resources. It extends the MetaData type and includes all properties of a Collectible object except for the data property. This type is used to provide additional information about the paginated collection, such as pagination details, without including the actual resource data.
|
|
39
|
+
* @example
|
|
40
|
+
* const paginatedMeta: PaginatedMetaData = {
|
|
41
|
+
* pagination: {
|
|
42
|
+
* currentPage: 1,
|
|
43
|
+
* total: 100
|
|
44
|
+
* },
|
|
45
|
+
* timestamp: '2024-06-01T12:00:00Z'
|
|
46
|
+
* };
|
|
47
|
+
*/
|
|
36
48
|
type PaginatedMetaData<R extends Collectible = Collectible> = MetaData & Omit<R, 'data'>;
|
|
49
|
+
/**
|
|
50
|
+
* @description A type that represents the body of a response for a collection of resources. It includes a data property, which can be either an array of ResourceData objects or a Collectible object, and an optional meta property that can contain any additional metadata related to the response. The type is generic and can be used to define the structure of the response body for API endpoints that return collections of resources.
|
|
51
|
+
* @example
|
|
52
|
+
* const collectionResponse: CollectionBody = {
|
|
53
|
+
* data: [
|
|
54
|
+
* { id: 1, name: 'Resource 1' },
|
|
55
|
+
* { id: 2, name: 'Resource 2' }
|
|
56
|
+
* ],
|
|
57
|
+
* meta: {
|
|
58
|
+
* pagination: {
|
|
59
|
+
* currentPage: 1,
|
|
60
|
+
* total: 2
|
|
61
|
+
* }
|
|
62
|
+
* }
|
|
63
|
+
* };
|
|
64
|
+
*/
|
|
37
65
|
interface ResponseDataCollection<R extends Collectible | undefined = undefined> extends ResourceData {
|
|
38
66
|
data: R extends Collectible ? R['data'] : R;
|
|
39
67
|
meta?: R extends Collectible ? PaginatedMetaData<R> | undefined : undefined;
|
|
@@ -80,6 +108,23 @@ type CollectionBody<R extends ResourceData[] | Collectible = ResourceData[]> = R
|
|
|
80
108
|
type ResourceBody<R extends ResourceData | NonCollectible = ResourceData> = ResponseData<R extends NonCollectible ? R : {
|
|
81
109
|
data: R;
|
|
82
110
|
}>;
|
|
111
|
+
/**
|
|
112
|
+
* @description A type that represents the body of a response for either a single resource or a collection of resources.
|
|
113
|
+
* It can be either a ResourceData object, an array of ResourceData objects, a NonCollectible object, or a Collectible object.
|
|
114
|
+
* The type also includes the meta property, which is optional and can contain any additional metadata related to the response.
|
|
115
|
+
* @example
|
|
116
|
+
* const genericResponse: GenericBody = {
|
|
117
|
+
* data: {
|
|
118
|
+
* id: 1,
|
|
119
|
+
* name: 'Resource Name',
|
|
120
|
+
* description: 'Resource Description'
|
|
121
|
+
* },
|
|
122
|
+
* meta: {
|
|
123
|
+
* timestamp: '2024-06-01T12:00:00Z'
|
|
124
|
+
* }
|
|
125
|
+
* };
|
|
126
|
+
*/
|
|
127
|
+
type GenericBody<R extends NonCollectible | Collectible | ResourceData = ResourceData> = ResponseData<R>;
|
|
83
128
|
/**
|
|
84
129
|
* @description A type that represents the pagination information for a collection of resources. It includes properties such as currentPage, from, to, perPage, total, firstPage, lastPage, prevPage, and nextPage. All properties are optional and can be undefined if not applicable.
|
|
85
130
|
* @example
|
|
@@ -118,6 +163,67 @@ interface Cursor {
|
|
|
118
163
|
previous?: string | undefined;
|
|
119
164
|
next?: string | undefined;
|
|
120
165
|
}
|
|
166
|
+
interface Config {
|
|
167
|
+
/**
|
|
168
|
+
* @description The directory where resource files are stored. This is the location where the generated resource files will be saved. It should be a valid path on the file system.
|
|
169
|
+
*/
|
|
170
|
+
resourcesDir: string;
|
|
171
|
+
/**
|
|
172
|
+
* @description The directory where stub files are stored. Stub files are templates used for generating resource files. This should also be a valid path on the file system where the stub templates are located.
|
|
173
|
+
*/
|
|
174
|
+
stubsDir: string;
|
|
175
|
+
/**
|
|
176
|
+
* @description An object that defines the stub file names for different types of resources.
|
|
177
|
+
*/
|
|
178
|
+
stubs: {
|
|
179
|
+
/**
|
|
180
|
+
* @description The stub file name for a resource. This stub will be used when generating a resource file.
|
|
181
|
+
*/
|
|
182
|
+
resource: string;
|
|
183
|
+
/**
|
|
184
|
+
* @description The stub file name for a collection resource. This stub will be used when generating a collection resource file.
|
|
185
|
+
*/
|
|
186
|
+
collection: string;
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
//#endregion
|
|
190
|
+
//#region src/cli/actions.d.ts
|
|
191
|
+
declare class CliApp {
|
|
192
|
+
command: Command;
|
|
193
|
+
private config;
|
|
194
|
+
constructor(config?: Partial<Config>);
|
|
195
|
+
/**
|
|
196
|
+
* Utility to ensure directory exists
|
|
197
|
+
*
|
|
198
|
+
* @param filePath
|
|
199
|
+
*/
|
|
200
|
+
ensureDirectory(filePath: string): void;
|
|
201
|
+
/**
|
|
202
|
+
* Utility to generate file from stub
|
|
203
|
+
*
|
|
204
|
+
* @param stubPath
|
|
205
|
+
* @param outputPath
|
|
206
|
+
* @param replacements
|
|
207
|
+
*/
|
|
208
|
+
generateFile(stubPath: string, outputPath: string, replacements: Record<string, string>, options?: any): string;
|
|
209
|
+
/**
|
|
210
|
+
* Command to create a new resource or resource collection file
|
|
211
|
+
*
|
|
212
|
+
* @param name
|
|
213
|
+
* @param options
|
|
214
|
+
*/
|
|
215
|
+
makeResource(name: string, options: any): {
|
|
216
|
+
name: string;
|
|
217
|
+
path: string;
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
//#endregion
|
|
221
|
+
//#region src/cli/commands/MakeResource.d.ts
|
|
222
|
+
declare class MakeResource extends Command<CliApp> {
|
|
223
|
+
protected signature: string;
|
|
224
|
+
protected description: string;
|
|
225
|
+
handle(): Promise<undefined>;
|
|
226
|
+
}
|
|
121
227
|
//#endregion
|
|
122
228
|
//#region src/ServerResponse.d.ts
|
|
123
229
|
declare class ServerResponse<R extends NonCollectible | Collectible | ResourceData[] | ResourceData = ResourceData> {
|
|
@@ -325,4 +431,62 @@ declare class Resource<R extends ResourceData | NonCollectible = ResourceData> {
|
|
|
325
431
|
finally(onfinally?: (() => void) | null): Promise<void>;
|
|
326
432
|
}
|
|
327
433
|
//#endregion
|
|
328
|
-
|
|
434
|
+
//#region src/GenericResource.d.ts
|
|
435
|
+
/**
|
|
436
|
+
* GenericResource class to handle API resource transformation and response building
|
|
437
|
+
*/
|
|
438
|
+
declare class GenericResource<R extends NonCollectible | Collectible | ResourceData = ResourceData, T extends ResourceData = any> {
|
|
439
|
+
private res?;
|
|
440
|
+
[key: string]: any;
|
|
441
|
+
body: GenericBody<R>;
|
|
442
|
+
resource: R;
|
|
443
|
+
collects?: typeof Resource<T>;
|
|
444
|
+
private called;
|
|
445
|
+
constructor(rsc: R, res?: Response | undefined);
|
|
446
|
+
/**
|
|
447
|
+
* Get the original resource data
|
|
448
|
+
*/
|
|
449
|
+
data(): R;
|
|
450
|
+
/**
|
|
451
|
+
* Convert resource to JSON response format
|
|
452
|
+
*
|
|
453
|
+
* @returns
|
|
454
|
+
*/
|
|
455
|
+
json(): this;
|
|
456
|
+
/**
|
|
457
|
+
* Convert resource to array format (for collections)
|
|
458
|
+
*
|
|
459
|
+
* @returns
|
|
460
|
+
*/
|
|
461
|
+
toArray(): any;
|
|
462
|
+
/**
|
|
463
|
+
* Add additional properties to the response body
|
|
464
|
+
*
|
|
465
|
+
* @param extra Additional properties to merge into the response body
|
|
466
|
+
* @returns
|
|
467
|
+
*/
|
|
468
|
+
additional<X extends Record<string, any>>(extra: X): this;
|
|
469
|
+
response(): ServerResponse<GenericBody<R>>;
|
|
470
|
+
response(res: H3Event['res']): ServerResponse<GenericBody<R>>;
|
|
471
|
+
/**
|
|
472
|
+
* Promise-like then method to allow chaining with async/await or .then() syntax
|
|
473
|
+
*
|
|
474
|
+
* @param onfulfilled Callback to handle the fulfilled state of the promise, receiving the response body
|
|
475
|
+
* @param onrejected Callback to handle the rejected state of the promise, receiving the error reason
|
|
476
|
+
* @returns A promise that resolves to the result of the onfulfilled or onrejected callback
|
|
477
|
+
*/
|
|
478
|
+
then<TResult1 = GenericBody<R>, TResult2 = never>(onfulfilled?: ((value: GenericBody<R>) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
479
|
+
}
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/utility.d.ts
|
|
482
|
+
/**
|
|
483
|
+
* Define the configuration for the package
|
|
484
|
+
*
|
|
485
|
+
* @param userConfig The user configuration to override the default configuration
|
|
486
|
+
* @returns The merged configuration object
|
|
487
|
+
*/
|
|
488
|
+
declare const defineConfig: (userConfig?: Partial<Omit<Config, "stubs">> & {
|
|
489
|
+
stubs?: Partial<Config["stubs"]>;
|
|
490
|
+
}) => Config;
|
|
491
|
+
//#endregion
|
|
492
|
+
export { ApiResource, CliApp, Collectible, CollectionBody, Config, Cursor, GenericBody, GenericResource, MakeResource, MetaData, NonCollectible, PaginatedMetaData, Pagination, Resource, ResourceBody, ResourceCollection, ResourceData, ResourceDef, ResponseData, ResponseDataCollection, ServerResponse, defineConfig };
|
package/dist/index.mjs
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import path, { dirname, join } from "path";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "fs";
|
|
3
|
+
import { createRequire } from "module";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import { Command } from "@h3ravel/musket";
|
|
6
|
+
|
|
1
7
|
//#region src/ApiResource.ts
|
|
2
8
|
/**
|
|
3
9
|
* ApiResource function to return the Resource instance
|
|
@@ -9,6 +15,175 @@ function ApiResource(instance) {
|
|
|
9
15
|
return instance;
|
|
10
16
|
}
|
|
11
17
|
|
|
18
|
+
//#endregion
|
|
19
|
+
//#region src/utility.ts
|
|
20
|
+
const __dirname = /* @__PURE__ */ path.dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
let stubsDir = path.resolve(__dirname, "../node_modules/resora/stubs");
|
|
22
|
+
if (!existsSync(stubsDir)) stubsDir = path.resolve(__dirname, "../stubs");
|
|
23
|
+
/**
|
|
24
|
+
* Define the configuration for the package
|
|
25
|
+
*
|
|
26
|
+
* @param userConfig The user configuration to override the default configuration
|
|
27
|
+
* @returns The merged configuration object
|
|
28
|
+
*/
|
|
29
|
+
const defineConfig = (userConfig = {}) => {
|
|
30
|
+
return Object.assign({
|
|
31
|
+
resourcesDir: "src/resources",
|
|
32
|
+
stubsDir,
|
|
33
|
+
stubs: {
|
|
34
|
+
resource: "resource.stub",
|
|
35
|
+
collection: "resource.collection.stub"
|
|
36
|
+
}
|
|
37
|
+
}, userConfig, { stubs: Object.assign({
|
|
38
|
+
resource: "resource.stub",
|
|
39
|
+
collection: "resource.collection.stub"
|
|
40
|
+
}, userConfig.stubs || {}) });
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/cli/actions.ts
|
|
45
|
+
var CliApp = class {
|
|
46
|
+
command;
|
|
47
|
+
config = {};
|
|
48
|
+
constructor(config = {}) {
|
|
49
|
+
this.config = defineConfig(config);
|
|
50
|
+
const require = createRequire(import.meta.url);
|
|
51
|
+
const possibleConfigPaths = [
|
|
52
|
+
join(process.cwd(), "resora.config.ts"),
|
|
53
|
+
join(process.cwd(), "resora.config.js"),
|
|
54
|
+
join(process.cwd(), "resora.config.cjs")
|
|
55
|
+
];
|
|
56
|
+
for (const configPath of possibleConfigPaths) if (existsSync(configPath)) try {
|
|
57
|
+
const { default: userConfig } = require(configPath);
|
|
58
|
+
Object.assign(this.config, defineConfig(userConfig));
|
|
59
|
+
break;
|
|
60
|
+
} catch (e) {
|
|
61
|
+
console.error(`Error loading config file at ${configPath}:`, e);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Utility to ensure directory exists
|
|
66
|
+
*
|
|
67
|
+
* @param filePath
|
|
68
|
+
*/
|
|
69
|
+
ensureDirectory(filePath) {
|
|
70
|
+
const dir = dirname(filePath);
|
|
71
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Utility to generate file from stub
|
|
75
|
+
*
|
|
76
|
+
* @param stubPath
|
|
77
|
+
* @param outputPath
|
|
78
|
+
* @param replacements
|
|
79
|
+
*/
|
|
80
|
+
generateFile(stubPath, outputPath, replacements, options) {
|
|
81
|
+
if (existsSync(outputPath) && !options?.force) {
|
|
82
|
+
this.command.error(`Error: ${outputPath} already exists.`);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
} else if (existsSync(outputPath) && options?.force) rmSync(outputPath);
|
|
85
|
+
let content = readFileSync(stubPath, "utf-8");
|
|
86
|
+
for (const [key, value] of Object.entries(replacements)) content = content.replace(new RegExp(`{{${key}}}`, "g"), value);
|
|
87
|
+
this.ensureDirectory(outputPath);
|
|
88
|
+
writeFileSync(outputPath, content);
|
|
89
|
+
return outputPath;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Command to create a new resource or resource collection file
|
|
93
|
+
*
|
|
94
|
+
* @param name
|
|
95
|
+
* @param options
|
|
96
|
+
*/
|
|
97
|
+
makeResource(name, options) {
|
|
98
|
+
let resourceName = name;
|
|
99
|
+
if (options?.collection && !name.endsWith("Collection") && !name.endsWith("Resource")) resourceName += "Collection";
|
|
100
|
+
else if (!options?.collection && !name.endsWith("Resource") && !name.endsWith("Collection")) resourceName += "Resource";
|
|
101
|
+
const fileName = `${resourceName}.ts`;
|
|
102
|
+
const outputPath = join(this.config.resourcesDir, fileName);
|
|
103
|
+
const stubPath = join(this.config.stubsDir, options?.collection || name.endsWith("Collection") ? this.config.stubs.collection : this.config.stubs.resource);
|
|
104
|
+
if (!existsSync(stubPath)) {
|
|
105
|
+
this.command.error(`Error: Stub file ${stubPath} not found.`);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
const collectsName = resourceName.replace(/(Resource|Collection)$/, "") + "Resource";
|
|
109
|
+
const collects = `/**
|
|
110
|
+
* The resource that this collection collects.
|
|
111
|
+
*/
|
|
112
|
+
collects = ${collectsName}
|
|
113
|
+
`;
|
|
114
|
+
const collectsImport = `import ${collectsName} from './${collectsName}'\n`;
|
|
115
|
+
const hasCollects = (!!options?.collection || name.endsWith("Collection")) && existsSync(join(this.config.resourcesDir, `${collectsName}.ts`));
|
|
116
|
+
const path = this.generateFile(stubPath, outputPath, {
|
|
117
|
+
ResourceName: resourceName,
|
|
118
|
+
CollectionResourceName: resourceName.replace(/(Resource|Collection)$/, "") + "Resource",
|
|
119
|
+
"collects = Resource": hasCollects ? collects : "",
|
|
120
|
+
"import = Resource": hasCollects ? collectsImport : ""
|
|
121
|
+
}, options);
|
|
122
|
+
return {
|
|
123
|
+
name: resourceName,
|
|
124
|
+
path
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/cli/commands/MakeResource.ts
|
|
131
|
+
var MakeResource = class extends Command {
|
|
132
|
+
signature = `#create:
|
|
133
|
+
{resource : Generates a new resource file.
|
|
134
|
+
| {name : Name of the resource to create}
|
|
135
|
+
| {--c|collection : Make a resource collection}
|
|
136
|
+
| {--force : Create the resource or collection file even if it already exists.}
|
|
137
|
+
}
|
|
138
|
+
{collection : Create a new resource collection file.
|
|
139
|
+
| {name : Name of the resource collection to create}
|
|
140
|
+
| {--force : Create the resource or collection file even if it already exists.}
|
|
141
|
+
}
|
|
142
|
+
{all : Create both resource and collection files.
|
|
143
|
+
| {prefix : prefix of the resources to create, "Admin" will create AdminResource, AdminCollection}
|
|
144
|
+
| {--force : Create the resource or collection file even if it already exists.}
|
|
145
|
+
}
|
|
146
|
+
`;
|
|
147
|
+
description = "Create a new resource or resource collection file";
|
|
148
|
+
async handle() {
|
|
149
|
+
this.app.command = this;
|
|
150
|
+
let path = "";
|
|
151
|
+
const action = this.dictionary.name || this.dictionary.baseCommand;
|
|
152
|
+
if (["resource", "collection"].includes(action) && !this.argument("name")) return void this.error("Error: Name argument is required.");
|
|
153
|
+
if (action === "all" && !this.argument("prefix")) return void this.error("Error: Prefix argument is required.");
|
|
154
|
+
switch (action) {
|
|
155
|
+
case "resource":
|
|
156
|
+
({path} = this.app.makeResource(this.argument("name"), this.options()));
|
|
157
|
+
break;
|
|
158
|
+
case "collection":
|
|
159
|
+
({path} = this.app.makeResource(this.argument("name") + "Collection", this.options()));
|
|
160
|
+
break;
|
|
161
|
+
case "all": {
|
|
162
|
+
const o1 = this.app.makeResource(this.argument("prefix"), { force: this.option("force") });
|
|
163
|
+
const o2 = this.app.makeResource(this.argument("prefix") + "Collection", {
|
|
164
|
+
collection: true,
|
|
165
|
+
force: this.option("force")
|
|
166
|
+
});
|
|
167
|
+
path = `${o1.path}, ${o2.path}`;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
default: this.fail(`Unknown action: ${action}`);
|
|
171
|
+
}
|
|
172
|
+
this.success(`Created: ${path}`);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
//#endregion
|
|
177
|
+
//#region src/cli/logo.ts
|
|
178
|
+
var logo_default = String.raw`
|
|
179
|
+
_____
|
|
180
|
+
| __ \
|
|
181
|
+
| |__) |___ ___ ___ _ __ __ _
|
|
182
|
+
| _ // _ \/ __|/ _ \| '__/ _, |
|
|
183
|
+
| | \ \ __/\__ \ (_) | | | (_| |
|
|
184
|
+
|_| \_\___||___/\___/|_| \__,_|
|
|
185
|
+
`;
|
|
186
|
+
|
|
12
187
|
//#endregion
|
|
13
188
|
//#region src/ServerResponse.ts
|
|
14
189
|
var ServerResponse = class {
|
|
@@ -123,6 +298,113 @@ var ServerResponse = class {
|
|
|
123
298
|
}
|
|
124
299
|
};
|
|
125
300
|
|
|
301
|
+
//#endregion
|
|
302
|
+
//#region src/GenericResource.ts
|
|
303
|
+
/**
|
|
304
|
+
* GenericResource class to handle API resource transformation and response building
|
|
305
|
+
*/
|
|
306
|
+
var GenericResource = class {
|
|
307
|
+
body = { data: {} };
|
|
308
|
+
resource;
|
|
309
|
+
collects;
|
|
310
|
+
called = {};
|
|
311
|
+
constructor(rsc, res) {
|
|
312
|
+
this.res = res;
|
|
313
|
+
this.resource = rsc;
|
|
314
|
+
/**
|
|
315
|
+
* Copy properties from rsc to this instance for easy
|
|
316
|
+
* access, but only if data is not an array
|
|
317
|
+
*/
|
|
318
|
+
if (!Array.isArray(this.resource.data ?? this.resource)) {
|
|
319
|
+
for (const key of Object.keys(this.resource.data ?? this.resource)) if (!(key in this)) Object.defineProperty(this, key, {
|
|
320
|
+
enumerable: true,
|
|
321
|
+
configurable: true,
|
|
322
|
+
get: () => {
|
|
323
|
+
return this.resource.data?.[key] ?? this.resource[key];
|
|
324
|
+
},
|
|
325
|
+
set: (value) => {
|
|
326
|
+
if (this.resource.data && this.resource.data[key]) this.resource.data[key] = value;
|
|
327
|
+
else this.resource[key] = value;
|
|
328
|
+
}
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Get the original resource data
|
|
334
|
+
*/
|
|
335
|
+
data() {
|
|
336
|
+
return this.resource;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Convert resource to JSON response format
|
|
340
|
+
*
|
|
341
|
+
* @returns
|
|
342
|
+
*/
|
|
343
|
+
json() {
|
|
344
|
+
if (!this.called.json) {
|
|
345
|
+
this.called.json = true;
|
|
346
|
+
const resource = this.data();
|
|
347
|
+
let data = Array.isArray(resource) ? [...resource] : { ...resource };
|
|
348
|
+
if (Array.isArray(data) && this.collects) {
|
|
349
|
+
data = data.map((item) => new this.collects(item).data());
|
|
350
|
+
this.resource = data;
|
|
351
|
+
}
|
|
352
|
+
if (typeof data.data !== "undefined") data = data.data;
|
|
353
|
+
if (this.resource.pagination && data.data && Array.isArray(data.data)) delete data.pagination;
|
|
354
|
+
this.body = { data };
|
|
355
|
+
if (Array.isArray(this.body.data) && this.resource.pagination) this.body.meta = { pagination: this.resource.pagination };
|
|
356
|
+
}
|
|
357
|
+
return this;
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Convert resource to array format (for collections)
|
|
361
|
+
*
|
|
362
|
+
* @returns
|
|
363
|
+
*/
|
|
364
|
+
toArray() {
|
|
365
|
+
this.called.toArray = true;
|
|
366
|
+
this.json();
|
|
367
|
+
let data = Array.isArray(this.resource) ? [...this.resource] : { ...this.resource };
|
|
368
|
+
if (typeof data.data !== "undefined") data = data.data;
|
|
369
|
+
return data;
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Add additional properties to the response body
|
|
373
|
+
*
|
|
374
|
+
* @param extra Additional properties to merge into the response body
|
|
375
|
+
* @returns
|
|
376
|
+
*/
|
|
377
|
+
additional(extra) {
|
|
378
|
+
this.called.additional = true;
|
|
379
|
+
this.json();
|
|
380
|
+
delete extra.data;
|
|
381
|
+
delete extra.pagination;
|
|
382
|
+
this.body = {
|
|
383
|
+
...this.body,
|
|
384
|
+
...extra
|
|
385
|
+
};
|
|
386
|
+
return this;
|
|
387
|
+
}
|
|
388
|
+
response(res) {
|
|
389
|
+
this.called.toResponse = true;
|
|
390
|
+
return new ServerResponse(res ?? this.res, this.body);
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Promise-like then method to allow chaining with async/await or .then() syntax
|
|
394
|
+
*
|
|
395
|
+
* @param onfulfilled Callback to handle the fulfilled state of the promise, receiving the response body
|
|
396
|
+
* @param onrejected Callback to handle the rejected state of the promise, receiving the error reason
|
|
397
|
+
* @returns A promise that resolves to the result of the onfulfilled or onrejected callback
|
|
398
|
+
*/
|
|
399
|
+
then(onfulfilled, onrejected) {
|
|
400
|
+
this.called.then = true;
|
|
401
|
+
this.json();
|
|
402
|
+
const resolved = Promise.resolve(this.body).then(onfulfilled, onrejected);
|
|
403
|
+
if (this.res) this.res.send(this.body);
|
|
404
|
+
return resolved;
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
|
|
126
408
|
//#endregion
|
|
127
409
|
//#region src/ResourceCollection.ts
|
|
128
410
|
/**
|
|
@@ -365,4 +647,4 @@ var Resource = class {
|
|
|
365
647
|
};
|
|
366
648
|
|
|
367
649
|
//#endregion
|
|
368
|
-
export { ApiResource, Resource };
|
|
650
|
+
export { ApiResource, CliApp, GenericResource, MakeResource, Resource, ResourceCollection, ServerResponse, defineConfig };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "resora",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "A structured API response layer for Node.js and TypeScript with automatic JSON responses, collection support, and pagination handling.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"api",
|
|
@@ -37,9 +37,14 @@
|
|
|
37
37
|
},
|
|
38
38
|
"files": [
|
|
39
39
|
"dist",
|
|
40
|
+
"stubs",
|
|
41
|
+
"bin",
|
|
40
42
|
"README.md",
|
|
41
43
|
"LICENSE"
|
|
42
44
|
],
|
|
45
|
+
"bin": {
|
|
46
|
+
"resora": "bin/index.mjs"
|
|
47
|
+
},
|
|
43
48
|
"devDependencies": {
|
|
44
49
|
"@eslint/js": "^10.0.1",
|
|
45
50
|
"@eslint/markdown": "^7.5.1",
|
|
@@ -64,7 +69,11 @@
|
|
|
64
69
|
"engines": {
|
|
65
70
|
"node": ">=20.0.0"
|
|
66
71
|
},
|
|
72
|
+
"dependencies": {
|
|
73
|
+
"@h3ravel/musket": "^0.10.1"
|
|
74
|
+
},
|
|
67
75
|
"scripts": {
|
|
76
|
+
"cmd": "tsx src/cli/index.ts",
|
|
68
77
|
"lint": "eslint",
|
|
69
78
|
"test": "pnpm vitest",
|
|
70
79
|
"test:coverage": "pnpm vitest --coverage --watch=false",
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ResourceCollection } from 'resora'
|
|
2
|
+
{{import = Resource}}
|
|
3
|
+
/**
|
|
4
|
+
* {{ResourceName}}
|
|
5
|
+
*/
|
|
6
|
+
export default class {{ResourceName}} extends ResourceCollection {
|
|
7
|
+
{{collects = Resource}}
|
|
8
|
+
/**
|
|
9
|
+
* Transform the resource into a plain JavaScript object.
|
|
10
|
+
*
|
|
11
|
+
* @memberof {{ResourceName}}
|
|
12
|
+
*/
|
|
13
|
+
data () {
|
|
14
|
+
return this.toArray()
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Resource } from 'resora'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* {{ResourceName}}
|
|
5
|
+
*/
|
|
6
|
+
export default class {{ResourceName}} extends Resource {
|
|
7
|
+
/**
|
|
8
|
+
* Transform the resource into a plain JavaScript object.
|
|
9
|
+
*
|
|
10
|
+
* @memberof {{ResourceName}}
|
|
11
|
+
*/
|
|
12
|
+
data () {
|
|
13
|
+
return this.toArray()
|
|
14
|
+
}
|
|
15
|
+
}
|