spice-js 2.7.33 → 2.7.35

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.
@@ -222,11 +222,16 @@ function _getModels() {
222
222
  var models = {};
223
223
  require("fs").readdirSync(paths).forEach(function (file) {
224
224
  var model = require(_path2.default.join(paths, file));
225
- // Handle ESM default exports
226
- if (model.default) {
227
- model = model.default;
225
+ // `props` is a named export; the default export is the SpiceModel class,
226
+ // which does not carry it. Unwrapping to the default export first drops
227
+ // the schema off every model and leaves Swagger with empty definitions,
228
+ // so read the named export and only fall back to the default export for
229
+ // models that attach props there instead.
230
+ var props = model.props;
231
+ if (props == undefined && model.default != undefined) {
232
+ props = model.default.props;
228
233
  }
229
- models[file.split(".")[0]] = model.props;
234
+ models[file.split(".")[0]] = props;
230
235
  });
231
236
  return models;
232
237
  });
@@ -4,7 +4,6 @@ exports.__esModule = true;
4
4
  exports.default = void 0;
5
5
  var _lodash = _interopRequireDefault(require("lodash"));
6
6
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
7
- function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) { "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); } return f; })(e, t); }
8
7
  function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) { ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } } return n; }, _extends.apply(null, arguments); }
9
8
  function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
10
9
  function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
@@ -37,7 +36,9 @@ class RestHelper {
37
36
  ctx.status = 200;
38
37
  ctx.body = response;
39
38
  } catch (e) {
40
- console.log("Error at response: ", e, e.stack);
39
+ console.error("Error at response: ", e, e == null ? void 0 : e.stack);
40
+ ctx.status = 500;
41
+ ctx.body = RestHelper.prepare_response(RestHelper.FAILURE, (e == null ? void 0 : e.message) || e);
41
42
  }
42
43
  })();
43
44
  }
@@ -123,47 +124,65 @@ class RestHelper {
123
124
  var filename, filePath;
124
125
  if (download_type === "csv") {
125
126
  var _ret = yield* function* () {
126
- var {
127
- flatten
128
- } = yield Promise.resolve().then(() => _interopRequireWildcard(require("flat")));
129
127
  var rows = Array.isArray(cleaned) ? cleaned : [cleaned];
130
128
  var csvReady = rows.map(normalizeEmptyArraysForCsv);
131
- var flatRows = csvReady.map(row => flatten(row, {
132
- safe: false,
133
- delimiter: "."
134
- }));
129
+ var flattenToLeaves = function flattenToLeaves(value, prefix, output) {
130
+ if (prefix === void 0) {
131
+ prefix = "";
132
+ }
133
+ if (output === void 0) {
134
+ output = {};
135
+ }
136
+ if (Array.isArray(value)) {
137
+ if (value.length === 0 && prefix) {
138
+ output[prefix] = "";
139
+ return output;
140
+ }
141
+ value.forEach((item, index) => {
142
+ var path = prefix ? prefix + "." + index : "" + index;
143
+ flattenToLeaves(item, path, output);
144
+ });
145
+ return output;
146
+ }
147
+ if (value && typeof value === "object") {
148
+ var entries = Object.entries(value);
149
+ if (entries.length === 0 && prefix) {
150
+ output[prefix] = "";
151
+ return output;
152
+ }
153
+ for (var [key, item] of entries) {
154
+ var _path = prefix ? prefix + "." + key : key;
155
+ flattenToLeaves(item, _path, output);
156
+ }
157
+ return output;
158
+ }
159
+ if (prefix) {
160
+ output[prefix] = value;
161
+ }
162
+ return output;
163
+ };
164
+ var flatRows = csvReady.map(row => flattenToLeaves(row));
135
165
  var fieldSet = new Set();
136
- for (var r of flatRows) {
137
- Object.keys(r).forEach(k => fieldSet.add(k));
166
+ for (var row of flatRows) {
167
+ Object.keys(row).forEach(field => fieldSet.add(field));
138
168
  }
139
169
  var fields = Array.from(fieldSet).sort((a, b) => a.localeCompare(b, undefined, {
140
170
  numeric: true,
141
171
  sensitivity: "base"
142
172
  }));
143
-
144
- // Filter flattened fields to only include requested columns
145
173
  var rawUrl = ctx.originalUrl || ctx.request.url || "";
146
174
  var urlColumnsMatch = rawUrl.match(/[?&]columns=([^&]*)/);
147
- var requestedColumns = urlColumnsMatch ? decodeURIComponent(urlColumnsMatch[1]) : null;
175
+ var requestedColumns = urlColumnsMatch ? decodeURIComponent(urlColumnsMatch[1].replace(/\+/g, " ")) : null;
148
176
  if (requestedColumns) {
149
- var specs = requestedColumns.split(",").map(c => c.trim().replace(/`/g, "")).filter(c => c !== "");
150
- var filtered = [];
151
- for (var spec of specs) {
152
- if (!spec.includes(".")) {
153
- // Simple field: exact match
154
- if (fields.includes(spec)) filtered.push(spec);
155
- } else {
156
- // Nested field: build regex that allows numeric array indices between segments
157
- // and optionally matches all descendant fields.
158
- var segments = spec.split(".");
159
- var pattern = segments.map(s => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("(\\.\\d+)?\\.");
160
- var re = new RegExp("^" + pattern + "(\\..+)?$");
161
- for (var f of fields) {
162
- if (re.test(f)) filtered.push(f);
163
- }
164
- }
165
- }
166
- fields = filtered.length > 0 ? filtered : fields;
177
+ var requestedSpecs = requestedColumns.split(",").map(column => column.trim().replace(/`/g, "")).filter(Boolean);
178
+ var normalizePath = field => field.split(".").filter(segment => !/^\d+$/.test(segment)).join(".");
179
+ fields = fields.filter(field => {
180
+ var normalizedField = normalizePath(field);
181
+ return requestedSpecs.some(requestedField => {
182
+ var normalizedRequestedField = normalizePath(requestedField);
183
+ return normalizedField === normalizedRequestedField || normalizedField.startsWith(normalizedRequestedField + ".");
184
+ });
185
+ });
167
186
  }
168
187
  var csv = parse(flatRows, {
169
188
  fields,
@@ -178,7 +197,6 @@ class RestHelper {
178
197
  ctx.type = "text/csv; charset=utf-8";
179
198
  ctx.status = 200;
180
199
  var csvStream = fs.createReadStream(filePath);
181
- // Delete file after stream is finished
182
200
  csvStream.on("close", () => {
183
201
  fs.promises.unlink(filePath).catch(() => {});
184
202
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spice-js",
3
- "version": "2.7.33",
3
+ "version": "2.7.35",
4
4
  "description": "spice",
5
5
  "main": "build/index.js",
6
6
  "repository": {
@@ -244,11 +244,16 @@ async function getModels() {
244
244
  .readdirSync(paths)
245
245
  .forEach(function (file) {
246
246
  let model = require(path.join(paths, file));
247
- // Handle ESM default exports
248
- if (model.default) {
249
- model = model.default;
247
+ // `props` is a named export; the default export is the SpiceModel class,
248
+ // which does not carry it. Unwrapping to the default export first drops
249
+ // the schema off every model and leaves Swagger with empty definitions,
250
+ // so read the named export and only fall back to the default export for
251
+ // models that attach props there instead.
252
+ let props = model.props;
253
+ if (props == undefined && model.default != undefined) {
254
+ props = model.default.props;
250
255
  }
251
- models[file.split(".")[0]] = model.props;
256
+ models[file.split(".")[0]] = props;
252
257
  });
253
258
  return models;
254
259
  }
@@ -30,7 +30,12 @@ export default class RestHelper {
30
30
  ctx.status = 200;
31
31
  ctx.body = response;
32
32
  } catch (e) {
33
- console.log("Error at response: ", e, e.stack);
33
+ console.error("Error at response: ", e, e?.stack);
34
+ ctx.status = 500;
35
+ ctx.body = RestHelper.prepare_response(
36
+ RestHelper.FAILURE,
37
+ e?.message || e,
38
+ );
34
39
  }
35
40
  }
36
41
 
@@ -100,7 +105,6 @@ export default class RestHelper {
100
105
  try {
101
106
  const download_type = (ctx.request.query.format || "csv").toLowerCase();
102
107
  const include_id = ctx.request.query.include_id;
103
-
104
108
  const original = _.cloneDeep(ctx.data);
105
109
  const trimmed = deepTrimStrings(original);
106
110
  const cleaned = stripTopLevel(trimmed, {
@@ -108,78 +112,126 @@ export default class RestHelper {
108
112
  });
109
113
 
110
114
  let filename, filePath;
111
-
112
115
  if (download_type === "csv") {
113
- const { flatten } = await import("flat");
114
116
  const rows = Array.isArray(cleaned) ? cleaned : [cleaned];
115
-
117
+
116
118
  const csvReady = rows.map(normalizeEmptyArraysForCsv);
117
- const flatRows = csvReady.map((row) =>
118
- flatten(row, { safe: false, delimiter: "." }),
119
- );
120
-
119
+
120
+ const flattenToLeaves = (value, prefix = "", output = {}) => {
121
+ if (Array.isArray(value)) {
122
+ if (value.length === 0 && prefix) {
123
+ output[prefix] = "";
124
+ return output;
125
+ }
126
+
127
+ value.forEach((item, index) => {
128
+ const path = prefix ? `${prefix}.${index}` : `${index}`;
129
+ flattenToLeaves(item, path, output);
130
+ });
131
+
132
+ return output;
133
+ }
134
+
135
+ if (value && typeof value === "object") {
136
+ const entries = Object.entries(value);
137
+
138
+ if (entries.length === 0 && prefix) {
139
+ output[prefix] = "";
140
+ return output;
141
+ }
142
+
143
+ for (const [key, item] of entries) {
144
+ const path = prefix ? `${prefix}.${key}` : key;
145
+ flattenToLeaves(item, path, output);
146
+ }
147
+
148
+ return output;
149
+ }
150
+
151
+ if (prefix) {
152
+ output[prefix] = value;
153
+ }
154
+
155
+ return output;
156
+ };
157
+
158
+ const flatRows = csvReady.map((row) => flattenToLeaves(row));
159
+
121
160
  const fieldSet = new Set();
122
- for (const r of flatRows)
123
- Object.keys(r).forEach((k) => fieldSet.add(k));
161
+
162
+ for (const row of flatRows) {
163
+ Object.keys(row).forEach((field) => fieldSet.add(field));
164
+ }
165
+
124
166
  let fields = Array.from(fieldSet).sort((a, b) =>
125
- a.localeCompare(b, undefined, { numeric: true, sensitivity: "base" }),
167
+ a.localeCompare(b, undefined, {
168
+ numeric: true,
169
+ sensitivity: "base",
170
+ }),
126
171
  );
127
-
128
- // Filter flattened fields to only include requested columns
172
+
129
173
  const rawUrl = ctx.originalUrl || ctx.request.url || "";
130
174
  const urlColumnsMatch = rawUrl.match(/[?&]columns=([^&]*)/);
175
+
131
176
  const requestedColumns = urlColumnsMatch
132
- ? decodeURIComponent(urlColumnsMatch[1])
177
+ ? decodeURIComponent(urlColumnsMatch[1].replace(/\+/g, " "))
133
178
  : null;
179
+
134
180
  if (requestedColumns) {
135
- const specs = requestedColumns
181
+ const requestedSpecs = requestedColumns
136
182
  .split(",")
137
- .map((c) => c.trim().replace(/`/g, ""))
138
- .filter((c) => c !== "");
139
- const filtered = [];
140
- for (const spec of specs) {
141
- if (!spec.includes(".")) {
142
- // Simple field: exact match
143
- if (fields.includes(spec)) filtered.push(spec);
144
- } else {
145
- // Nested field: build regex that allows numeric array indices between segments
146
- // and optionally matches all descendant fields.
147
- const segments = spec.split(".");
148
- const pattern = segments
149
- .map((s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
150
- .join("(\\.\\d+)?\\.");
151
- const re = new RegExp("^" + pattern + "(\\..+)?$");
152
- for (const f of fields) {
153
- if (re.test(f)) filtered.push(f);
154
- }
155
- }
156
- }
157
- fields = filtered.length > 0 ? filtered : fields;
183
+ .map((column) => column.trim().replace(/`/g, ""))
184
+ .filter(Boolean);
185
+
186
+ const normalizePath = (field) =>
187
+ field
188
+ .split(".")
189
+ .filter((segment) => !/^\d+$/.test(segment))
190
+ .join(".");
191
+
192
+ fields = fields.filter((field) => {
193
+ const normalizedField = normalizePath(field);
194
+
195
+ return requestedSpecs.some((requestedField) => {
196
+ const normalizedRequestedField = normalizePath(requestedField);
197
+
198
+ return (
199
+ normalizedField === normalizedRequestedField ||
200
+ normalizedField.startsWith(`${normalizedRequestedField}.`)
201
+ );
202
+ });
203
+ });
158
204
  }
159
-
205
+
160
206
  const csv = parse(flatRows, {
161
207
  fields,
162
208
  defaultValue: "",
163
209
  excelStrings: true,
164
210
  });
165
-
166
- makeDirectory(`./storage/exports/csv`);
211
+
212
+ makeDirectory("./storage/exports/csv");
213
+
167
214
  filename = `${RestHelper.makeid(9)}.csv`;
168
215
  filePath = path.resolve(`./storage/exports/csv/${filename}`);
216
+
169
217
  await fs.promises.writeFile(filePath, csv, "utf8");
170
-
171
- ctx.set("Content-Disposition", `attachment; filename="${filename}"`);
218
+
219
+ ctx.set(
220
+ "Content-Disposition",
221
+ `attachment; filename="${filename}"`,
222
+ );
172
223
  ctx.type = "text/csv; charset=utf-8";
173
224
  ctx.status = 200;
225
+
174
226
  const csvStream = fs.createReadStream(filePath);
175
- // Delete file after stream is finished
227
+
176
228
  csvStream.on("close", () => {
177
229
  fs.promises.unlink(filePath).catch(() => {});
178
230
  });
231
+
179
232
  ctx.body = csvStream;
180
233
  return;
181
234
  }
182
-
183
235
  const jsonText = safeJSONStringify(cleaned, 2);
184
236
 
185
237
  makeDirectory(`./storage/exports/json`);
@@ -202,6 +254,7 @@ export default class RestHelper {
202
254
  }
203
255
  }
204
256
 
257
+
205
258
  static makeid(length) {
206
259
  var result = "";
207
260
  var characters =