@foxtware/mineral 0.1.0 → 0.1.1
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/.creds.yml.sample +5 -0
- package/README.md +1 -1
- package/_build_scripts/publish.js +85 -0
- package/api/logiwa/logiwa.constants.js +8 -0
- package/api/logiwa/logiwa.utils.js +53 -0
- package/api/logiwa/logiwaAuthGet.js +86 -0
- package/api/logiwa/logiwaInventoriesGet.js +67 -0
- package/api/logiwa/logiwaOrderGet.js +54 -0
- package/api/logiwa/logiwaOrdersGet.js +92 -0
- package/api/logiwa/logiwaProductGet.js +54 -0
- package/api/logiwa/logiwaProductsGet.js +80 -0
- package/api/logiwa/logiwaReportGetAvailableToPromise.js +179 -0
- package/api/logiwa/logiwaReportGetInventoryCalculation.js +62 -0
- package/api/logiwa/logiwaReportGetInventorySnapshot.js +66 -0
- package/api/logiwa/logiwaReportGetTotalInventory.js +72 -0
- package/api/logiwa/logiwaWebhookStatusGet.js +54 -0
- package/api/logiwa/logiwaWebhookSubscribe.js +64 -0
- package/api/logiwa/logiwaWebhookUnsubscribe.js +54 -0
- package/api/logiwa/logiwaWebhooksGet.js +48 -0
- package/api/shopify/_example.get.js +46 -0
- package/api/shopify/shopifyAbandonedCheckoutsGet.js +46 -0
- package/api/shopify/shopifyArticlesGet.js +46 -0
- package/api/shopify/shopifyBlogsGet.js +46 -0
- package/api/shopify/shopifyBulkOperationsGet.js +46 -0
- package/api/shopify/shopifyCatalogsGet.js +46 -0
- package/api/shopify/shopifyChannelsGet.js +46 -0
- package/api/shopify/shopifyCheckoutAndAccountsConfigurationsGet.js +46 -0
- package/api/shopify/shopifyCollectionsGet.js +46 -0
- package/api/shopify/shopifyCustomersGet.js +2 -0
- package/api/shopify/shopifyDeliveryCustomizationsGet.js +46 -0
- package/api/shopify/shopifyDeliveryProfilesGet.js +46 -0
- package/api/shopify/shopifyDiscountNodesGet.js +46 -0
- package/api/shopify/shopifyDisputesGet.js +46 -0
- package/api/shopify/shopifyDraftOrdersGet.js +46 -0
- package/api/shopify/shopifyEventsGet.js +46 -0
- package/api/shopify/shopifyFilesGet.js +46 -0
- package/api/shopify/shopifyFulfillmentOrdersGet.js +46 -0
- package/api/shopify/shopifyGiftCardsGet.js +46 -0
- package/api/shopify/shopifyInventoryItemsGet.js +46 -0
- package/api/shopify/shopifyLocationsGet.js +46 -0
- package/api/shopify/shopifyMarketingEventsGet.js +46 -0
- package/api/shopify/shopifyMarketsGet.js +46 -0
- package/api/shopify/shopifyMenusGet.js +46 -0
- package/api/shopify/shopifyMetafieldDefinitionsGet.js +50 -0
- package/api/shopify/shopifyMetaobjectDefinitionsGet.js +46 -0
- package/api/shopify/shopifyMetaobjectsGet.js +50 -0
- package/api/shopify/shopifyPagesGet.js +46 -0
- package/api/shopify/shopifyPaymentCustomizationsGet.js +46 -0
- package/api/shopify/shopifyProductFeedsGet.js +46 -0
- package/api/shopify/shopifyProductVariantsGet.js +46 -0
- package/api/shopify/shopifyProductsGet.js +46 -0
- package/api/shopify/shopifyPublicationsGet.js +46 -0
- package/api/shopify/shopifySegmentsGet.js +46 -0
- package/api/shopify/shopifyUrlRedirectsGet.js +46 -0
- package/api/shopify/shopifyWebhookSubscriptionsGet.js +46 -0
- package/api/utils.js +4 -3
- package/api/workspace.js +43 -0
- package/package.json +5 -3
- package/server.js +205 -105
package/server.js
CHANGED
|
@@ -1,45 +1,100 @@
|
|
|
1
|
-
require('dotenv').config();
|
|
2
|
-
|
|
3
1
|
const fs = require('fs');
|
|
4
|
-
const path = require('path');
|
|
5
2
|
const http = require('http');
|
|
6
3
|
const { respondJson, errorToReadable, getRequestBody, argsFromBody, funcApi } = require('./server.utils');
|
|
4
|
+
const { getWorkspace, setWorkspace, loadWorkspaceEnv, toAbsolutePath } = require('./api/workspace');
|
|
5
|
+
|
|
6
|
+
const MINERAL_API_DIR = `${ __dirname }/api`;
|
|
7
|
+
|
|
8
|
+
// --- Config ---
|
|
9
|
+
|
|
10
|
+
const splitCommaList = (value = '') => value
|
|
11
|
+
.split(',')
|
|
12
|
+
.map((item) => item.trim())
|
|
13
|
+
.filter(Boolean);
|
|
14
|
+
|
|
15
|
+
const readCliFlag = (flag) => {
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
const equalsPrefix = `${ flag }=`;
|
|
18
|
+
|
|
19
|
+
for (let index = 0; index < args.length; index++) {
|
|
20
|
+
if (args[index].startsWith(equalsPrefix)) {
|
|
21
|
+
return args[index].slice(equalsPrefix.length);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (args[index] === flag) {
|
|
25
|
+
return args[index + 1];
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const getApiDirs = (options = {}) => {
|
|
31
|
+
if (options.api_dirs) {
|
|
32
|
+
return options.api_dirs;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const fromCli = readCliFlag('--api_dirs');
|
|
36
|
+
if (fromCli) {
|
|
37
|
+
return splitCommaList(fromCli);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (process.env.MINERAL_API_DIRS) {
|
|
41
|
+
return splitCommaList(process.env.MINERAL_API_DIRS);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return [];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const getConfig = (options = {}) => ({
|
|
48
|
+
port: Number(options.port ?? process.env.PORT ?? 8000),
|
|
49
|
+
workspace: toAbsolutePath(
|
|
50
|
+
options.workspace
|
|
51
|
+
?? readCliFlag('--workspace')
|
|
52
|
+
?? process.env.MINERAL_WORKSPACE
|
|
53
|
+
?? process.cwd(),
|
|
54
|
+
),
|
|
55
|
+
api_dirs: getApiDirs(options),
|
|
56
|
+
});
|
|
7
57
|
|
|
8
|
-
|
|
58
|
+
// --- Handler discovery ---
|
|
9
59
|
|
|
10
|
-
const
|
|
60
|
+
const isHandlerFile = (fileName) => {
|
|
11
61
|
if (!fileName.endsWith('.js')) {
|
|
12
|
-
return
|
|
62
|
+
return false;
|
|
13
63
|
}
|
|
14
64
|
|
|
15
65
|
if (fileName.startsWith('_')) {
|
|
16
|
-
return
|
|
66
|
+
return false;
|
|
17
67
|
}
|
|
18
68
|
|
|
19
69
|
if (fileName.endsWith('.utils.js')) {
|
|
20
|
-
return
|
|
70
|
+
return false;
|
|
21
71
|
}
|
|
22
72
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
'validators.js',
|
|
26
|
-
]);
|
|
73
|
+
return true;
|
|
74
|
+
};
|
|
27
75
|
|
|
28
|
-
|
|
76
|
+
const routeNameFromFile = (filePath) => {
|
|
77
|
+
const fileName = filePath.split('/').pop();
|
|
78
|
+
return fileName.slice(0, -3);
|
|
29
79
|
};
|
|
30
80
|
|
|
31
|
-
const
|
|
32
|
-
|
|
81
|
+
const listJsFiles = (directory) => {
|
|
82
|
+
if (!fs.existsSync(directory)) {
|
|
83
|
+
return [];
|
|
84
|
+
}
|
|
85
|
+
|
|
33
86
|
const files = [];
|
|
87
|
+
const entries = fs.readdirSync(directory, { withFileTypes: true });
|
|
34
88
|
|
|
35
89
|
for (const entry of entries) {
|
|
36
|
-
const entryPath =
|
|
90
|
+
const entryPath = `${ directory }/${ entry.name }`;
|
|
91
|
+
|
|
37
92
|
if (entry.isDirectory()) {
|
|
38
|
-
files.push(...
|
|
93
|
+
files.push(...listJsFiles(entryPath));
|
|
39
94
|
continue;
|
|
40
95
|
}
|
|
41
96
|
|
|
42
|
-
if (entry.isFile()) {
|
|
97
|
+
if (entry.isFile() && isHandlerFile(entry.name)) {
|
|
43
98
|
files.push(entryPath);
|
|
44
99
|
}
|
|
45
100
|
}
|
|
@@ -47,92 +102,105 @@ const walkFilesRecursive = (directory) => {
|
|
|
47
102
|
return files;
|
|
48
103
|
};
|
|
49
104
|
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
105
|
+
const directoriesToScan = ({
|
|
106
|
+
workspace,
|
|
107
|
+
api_dirs,
|
|
108
|
+
}) => {
|
|
109
|
+
const extraDirs = api_dirs.map((dir) => (
|
|
110
|
+
dir.startsWith('/')
|
|
111
|
+
? dir
|
|
112
|
+
: `${ workspace }/${ dir }`
|
|
113
|
+
));
|
|
114
|
+
|
|
115
|
+
return [MINERAL_API_DIR, ...extraDirs];
|
|
116
|
+
};
|
|
53
117
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
118
|
+
const getFuncApiConfig = ({
|
|
119
|
+
moduleExports,
|
|
120
|
+
routeName,
|
|
121
|
+
}) => {
|
|
122
|
+
const { funcApiConfig } = moduleExports;
|
|
123
|
+
if (!funcApiConfig || typeof funcApiConfig !== 'object') {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
59
126
|
|
|
60
|
-
|
|
61
|
-
|
|
127
|
+
if (funcApiConfig[routeName]) {
|
|
128
|
+
return funcApiConfig[routeName];
|
|
129
|
+
}
|
|
62
130
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
131
|
+
const exportNames = Object.keys(moduleExports).filter((key) => key !== 'funcApiConfig');
|
|
132
|
+
const configIsShared = !exportNames.some((name) => funcApiConfig[name]);
|
|
66
133
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
.filter(([exportName, exportValue]) => exportName !== 'funcApiConfig' && typeof exportValue === 'function')
|
|
72
|
-
.map(([exportName]) => exportName);
|
|
73
|
-
|
|
74
|
-
const getFuncApiConfigForExport = (exportName) => {
|
|
75
|
-
if (!funcApiConfig || typeof funcApiConfig !== 'object') {
|
|
76
|
-
return undefined;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
const configByExportName = funcApiConfig[exportName];
|
|
80
|
-
if (configByExportName && typeof configByExportName === 'object' && !Array.isArray(configByExportName)) {
|
|
81
|
-
return configByExportName;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// If config isn't keyed by function names and this export matches the filename,
|
|
85
|
-
// treat funcApiConfig as the config for that function.
|
|
86
|
-
const configIsKeyedByFunctionName = functionExportNames.some((functionExportName) => funcApiConfig[functionExportName] !== undefined);
|
|
87
|
-
if (!configIsKeyedByFunctionName && exportName === fileBaseName) {
|
|
88
|
-
return funcApiConfig;
|
|
89
|
-
}
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const exportFuncApiConfig = getFuncApiConfigForExport(fileBaseName);
|
|
93
|
-
const handler = exportFuncApiConfig
|
|
94
|
-
? funcApi(exportedValue, exportFuncApiConfig)
|
|
95
|
-
: exportedValue;
|
|
96
|
-
|
|
97
|
-
const routePath = `/${ fileBaseName }`;
|
|
98
|
-
if (routeToHandler.has(routePath)) {
|
|
99
|
-
const existing = routeToHandler.get(routePath);
|
|
100
|
-
throw new Error(`Duplicate handler route '${ routePath }' from ${ filePath } and ${ existing.filePath }`);
|
|
101
|
-
}
|
|
134
|
+
if (configIsShared) {
|
|
135
|
+
return funcApiConfig;
|
|
136
|
+
}
|
|
137
|
+
};
|
|
102
138
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
139
|
+
const addHandlerFromFile = (filePath, handlers) => {
|
|
140
|
+
const moduleExports = require(filePath);
|
|
141
|
+
if (!moduleExports || typeof moduleExports !== 'object') {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const routeName = routeNameFromFile(filePath);
|
|
146
|
+
const handlerFn = moduleExports[routeName];
|
|
147
|
+
if (typeof handlerFn !== 'function') {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const funcApiConfig = getFuncApiConfig({
|
|
152
|
+
moduleExports,
|
|
153
|
+
routeName,
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
const route = `/${ routeName }`;
|
|
157
|
+
if (handlers.has(route)) {
|
|
158
|
+
const existing = handlers.get(route);
|
|
159
|
+
throw new Error(`Duplicate route '${ route }' from ${ filePath } and ${ existing.filePath }`);
|
|
109
160
|
}
|
|
110
161
|
|
|
111
|
-
|
|
162
|
+
handlers.set(route, {
|
|
163
|
+
filePath,
|
|
164
|
+
routeName,
|
|
165
|
+
handler: funcApiConfig ? funcApi(handlerFn, funcApiConfig) : handlerFn,
|
|
166
|
+
usesFuncApi: Boolean(funcApiConfig),
|
|
167
|
+
});
|
|
112
168
|
};
|
|
113
169
|
|
|
114
|
-
const
|
|
170
|
+
const loadHandlers = (config) => {
|
|
171
|
+
const handlers = new Map();
|
|
172
|
+
|
|
173
|
+
for (const directory of directoriesToScan(config)) {
|
|
174
|
+
for (const filePath of listJsFiles(directory)) {
|
|
175
|
+
addHandlerFromFile(filePath, handlers);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
115
178
|
|
|
116
|
-
|
|
179
|
+
return handlers;
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// --- HTTP ---
|
|
183
|
+
|
|
184
|
+
const createServer = (handlers) => http.createServer(async (req, res) => {
|
|
117
185
|
if (req.method === 'GET' && req.url === '/') {
|
|
118
|
-
const routes = Array.from(handlers.keys()).sort();
|
|
119
186
|
respondJson(res, 200, {
|
|
120
187
|
ok: true,
|
|
121
188
|
data: {
|
|
122
|
-
routes,
|
|
189
|
+
routes: [...handlers.keys()].sort(),
|
|
190
|
+
workspace: getWorkspace(),
|
|
123
191
|
},
|
|
124
192
|
});
|
|
125
193
|
return;
|
|
126
194
|
}
|
|
127
195
|
|
|
128
|
-
const
|
|
129
|
-
const
|
|
130
|
-
if (!
|
|
196
|
+
const route = (req.url || '/').split('?')[0];
|
|
197
|
+
const handler = handlers.get(route);
|
|
198
|
+
if (!handler) {
|
|
131
199
|
respondJson(res, 404, {
|
|
132
200
|
ok: false,
|
|
133
201
|
error: {
|
|
134
202
|
code: 'NOT_FOUND',
|
|
135
|
-
message: `No handler found for route ${
|
|
203
|
+
message: `No handler found for route ${ route }`,
|
|
136
204
|
},
|
|
137
205
|
});
|
|
138
206
|
return;
|
|
@@ -141,19 +209,12 @@ const server = http.createServer(async (req, res) => {
|
|
|
141
209
|
try {
|
|
142
210
|
const body = await getRequestBody(req);
|
|
143
211
|
const args = argsFromBody(body);
|
|
144
|
-
const result =
|
|
145
|
-
? await
|
|
146
|
-
|
|
147
|
-
res,
|
|
148
|
-
body,
|
|
149
|
-
args,
|
|
150
|
-
})
|
|
151
|
-
: await matchedHandler.handler(...args);
|
|
212
|
+
const result = handler.usesFuncApi
|
|
213
|
+
? await handler.handler({ req, res, body, args })
|
|
214
|
+
: await handler.handler(...args);
|
|
152
215
|
|
|
153
216
|
if (result === undefined) {
|
|
154
|
-
respondJson(res, 200, {
|
|
155
|
-
ok: true,
|
|
156
|
-
});
|
|
217
|
+
respondJson(res, 200, { ok: true });
|
|
157
218
|
return;
|
|
158
219
|
}
|
|
159
220
|
|
|
@@ -170,25 +231,64 @@ const server = http.createServer(async (req, res) => {
|
|
|
170
231
|
}
|
|
171
232
|
});
|
|
172
233
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
234
|
+
// --- Start ---
|
|
235
|
+
|
|
236
|
+
let server = null;
|
|
237
|
+
let handlers = null;
|
|
238
|
+
|
|
239
|
+
const logStartup = ({
|
|
240
|
+
port,
|
|
241
|
+
workspace,
|
|
242
|
+
api_dirs,
|
|
243
|
+
handlers: routeHandlers,
|
|
244
|
+
}) => {
|
|
245
|
+
console.log(`Mineral server running on port ${ port }`);
|
|
246
|
+
console.log(`Workspace: ${ workspace }`);
|
|
247
|
+
|
|
248
|
+
if (api_dirs.length) {
|
|
249
|
+
console.log('Extra API dirs:', api_dirs.join(', '));
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
console.log('Registered routes:');
|
|
253
|
+
for (const route of [...routeHandlers.keys()].sort()) {
|
|
254
|
+
console.log(route);
|
|
255
|
+
}
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const startServer = (options = {}) => {
|
|
259
|
+
const config = getConfig(options);
|
|
260
|
+
|
|
261
|
+
setWorkspace(config.workspace);
|
|
262
|
+
loadWorkspaceEnv();
|
|
263
|
+
|
|
264
|
+
handlers = loadHandlers(config);
|
|
265
|
+
server = createServer(handlers);
|
|
266
|
+
|
|
267
|
+
server.listen(config.port, () => logStartup({
|
|
268
|
+
...config,
|
|
269
|
+
handlers,
|
|
270
|
+
}));
|
|
183
271
|
|
|
184
272
|
return server;
|
|
185
273
|
};
|
|
186
274
|
|
|
187
275
|
module.exports = {
|
|
188
|
-
server,
|
|
189
|
-
handlers,
|
|
190
|
-
loadHandlers,
|
|
191
276
|
startServer,
|
|
277
|
+
loadHandlers,
|
|
278
|
+
get server() {
|
|
279
|
+
if (!server) {
|
|
280
|
+
startServer();
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return server;
|
|
284
|
+
},
|
|
285
|
+
get handlers() {
|
|
286
|
+
if (!handlers) {
|
|
287
|
+
startServer();
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return handlers;
|
|
291
|
+
},
|
|
192
292
|
};
|
|
193
293
|
|
|
194
294
|
if (require.main === module) {
|