@jskit-ai/json-rest-api-core 0.1.91 → 0.1.92
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/package.descriptor.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export default Object.freeze({
|
|
2
2
|
packageVersion: 1,
|
|
3
3
|
packageId: "@jskit-ai/json-rest-api-core",
|
|
4
|
-
version: "0.1.
|
|
4
|
+
version: "0.1.92",
|
|
5
5
|
kind: "runtime",
|
|
6
6
|
description: "Shared internal json-rest-api host runtime with autofilter, query-projection, and row-policy support.",
|
|
7
7
|
dependsOn: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jskit-ai/json-rest-api-core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.92",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"test": "node --test"
|
|
@@ -11,6 +11,6 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"hooked-api": "1.x.x",
|
|
13
13
|
"json-rest-api": "^1.0.27",
|
|
14
|
-
"@jskit-ai/kernel": "0.1.
|
|
14
|
+
"@jskit-ai/kernel": "0.1.147"
|
|
15
15
|
}
|
|
16
16
|
}
|
|
@@ -54,6 +54,8 @@ const JSON_REST_RESERVED_QUERY_KEYS = Object.freeze(new Set([
|
|
|
54
54
|
"fields"
|
|
55
55
|
]));
|
|
56
56
|
const JSON_REST_CALENDAR_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/u;
|
|
57
|
+
const JSON_REST_DATE_TIME_PATTERN = /^\d{4}-\d{2}-\d{2}T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?(?:Z|[+-](?:[01]\d|2[0-3]):[0-5]\d)$/u;
|
|
58
|
+
const JSON_REST_DATABASE_UTC_DATE_TIME_PATTERN = /^(\d{4}-\d{2}-\d{2}) ((?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d(?:\.\d+)?)$/u;
|
|
57
59
|
|
|
58
60
|
function isPlainJsonRestObject(value) {
|
|
59
61
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
@@ -139,6 +141,20 @@ function serializeJsonRestCalendarDate(value) {
|
|
|
139
141
|
return canonical;
|
|
140
142
|
}
|
|
141
143
|
|
|
144
|
+
function resolveCanonicalDateTime(value) {
|
|
145
|
+
if (value instanceof Date) {
|
|
146
|
+
return Number.isNaN(value.getTime()) ? "" : value.toISOString();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const normalized = typeof value === "string" ? value.trim() : "";
|
|
150
|
+
if (JSON_REST_DATE_TIME_PATTERN.test(normalized)) {
|
|
151
|
+
return normalized;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const databaseMatch = JSON_REST_DATABASE_UTC_DATE_TIME_PATTERN.exec(normalized);
|
|
155
|
+
return databaseMatch ? `${databaseMatch[1]}T${databaseMatch[2]}Z` : "";
|
|
156
|
+
}
|
|
157
|
+
|
|
142
158
|
function applyJsonRestCalendarDateWriteSerializers(scopeOptions = {}) {
|
|
143
159
|
const schema = normalizeJsonRestObject(scopeOptions.schema);
|
|
144
160
|
for (const fieldDefinition of Object.values(schema)) {
|
|
@@ -160,7 +176,7 @@ function applyJsonRestCalendarDateWriteSerializers(scopeOptions = {}) {
|
|
|
160
176
|
}
|
|
161
177
|
}
|
|
162
178
|
|
|
163
|
-
function
|
|
179
|
+
function normalizeJsonRestTemporalEntry(entry = null, scopes = {}) {
|
|
164
180
|
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
165
181
|
return entry;
|
|
166
182
|
}
|
|
@@ -170,15 +186,16 @@ function normalizeJsonRestCalendarDateEntry(entry = null, scopes = {}) {
|
|
|
170
186
|
);
|
|
171
187
|
const attributes = normalizeJsonRestObject(entry.attributes);
|
|
172
188
|
for (const [fieldName, definition] of Object.entries(schema)) {
|
|
173
|
-
if (
|
|
174
|
-
normalizeJsonRestText(definition?.type).toLowerCase() !== "date" ||
|
|
175
|
-
!Object.hasOwn(attributes, fieldName) ||
|
|
176
|
-
attributes[fieldName] == null
|
|
177
|
-
) {
|
|
189
|
+
if (!Object.hasOwn(attributes, fieldName) || attributes[fieldName] == null) {
|
|
178
190
|
continue;
|
|
179
191
|
}
|
|
180
192
|
|
|
181
|
-
const
|
|
193
|
+
const fieldType = normalizeJsonRestText(definition?.type).toLowerCase();
|
|
194
|
+
const canonical = fieldType === "date"
|
|
195
|
+
? resolveCanonicalCalendarDate(attributes[fieldName])
|
|
196
|
+
: fieldType === "datetime"
|
|
197
|
+
? resolveCanonicalDateTime(attributes[fieldName])
|
|
198
|
+
: "";
|
|
182
199
|
if (canonical) {
|
|
183
200
|
attributes[fieldName] = canonical;
|
|
184
201
|
}
|
|
@@ -187,31 +204,31 @@ function normalizeJsonRestCalendarDateEntry(entry = null, scopes = {}) {
|
|
|
187
204
|
return entry;
|
|
188
205
|
}
|
|
189
206
|
|
|
190
|
-
function
|
|
207
|
+
function normalizeJsonRestTemporalDocument(document = null, scopes = {}) {
|
|
191
208
|
if (!document || typeof document !== "object" || Array.isArray(document)) {
|
|
192
209
|
return document;
|
|
193
210
|
}
|
|
194
211
|
|
|
195
212
|
const data = Array.isArray(document.data) ? document.data : [document.data];
|
|
196
213
|
for (const entry of data) {
|
|
197
|
-
|
|
214
|
+
normalizeJsonRestTemporalEntry(entry, scopes);
|
|
198
215
|
}
|
|
199
216
|
for (const entry of Array.isArray(document.included) ? document.included : []) {
|
|
200
|
-
|
|
217
|
+
normalizeJsonRestTemporalEntry(entry, scopes);
|
|
201
218
|
}
|
|
202
219
|
return document;
|
|
203
220
|
}
|
|
204
221
|
|
|
205
|
-
const
|
|
206
|
-
name: "jskit-
|
|
222
|
+
const JsonRestTemporalPlugin = Object.freeze({
|
|
223
|
+
name: "jskit-temporal",
|
|
207
224
|
dependencies: ["rest-api"],
|
|
208
225
|
install({ addHook, scopes }) {
|
|
209
226
|
addHook("finish", "normalizeCalendarDateDocuments", {}, ({ context }) => {
|
|
210
227
|
if (context?.record) {
|
|
211
|
-
|
|
228
|
+
normalizeJsonRestTemporalDocument(context.record, scopes);
|
|
212
229
|
}
|
|
213
230
|
if (context?.responseRecord) {
|
|
214
|
-
|
|
231
|
+
normalizeJsonRestTemporalDocument(context.responseRecord, scopes);
|
|
215
232
|
}
|
|
216
233
|
});
|
|
217
234
|
}
|
|
@@ -815,7 +832,7 @@ async function createJsonRestApiHost({ knex }) {
|
|
|
815
832
|
},
|
|
816
833
|
presets: JSON_REST_AUTOFILTER_PRESETS
|
|
817
834
|
});
|
|
818
|
-
await api.use(
|
|
835
|
+
await api.use(JsonRestTemporalPlugin);
|
|
819
836
|
|
|
820
837
|
return api;
|
|
821
838
|
}
|
|
@@ -641,7 +641,7 @@ test("createJsonRestApiHost installs json-rest-api query projections", async ()
|
|
|
641
641
|
assert.equal(typeof api.resources.projectionContacts.vars.queryFields.displayName.select, "function");
|
|
642
642
|
});
|
|
643
643
|
|
|
644
|
-
test("createJsonRestApiHost returns
|
|
644
|
+
test("createJsonRestApiHost returns JSON-native temporal values from database records", async () => {
|
|
645
645
|
const fakeKnex = Object.assign(() => {}, {
|
|
646
646
|
client: {
|
|
647
647
|
config: {
|
|
@@ -697,7 +697,7 @@ test("createJsonRestApiHost returns calendar dates as YYYY-MM-DD without changin
|
|
|
697
697
|
});
|
|
698
698
|
|
|
699
699
|
assert.equal(record.data.attributes.publishedOn, "2024-02-29");
|
|
700
|
-
assert.equal(record.data.attributes.scheduledAt,
|
|
700
|
+
assert.equal(record.data.attributes.scheduledAt, "2024-02-29T12:34:56.000Z");
|
|
701
701
|
assert.equal(record.data.attributes.opensAt, "09:45:00");
|
|
702
702
|
assert.equal(record.included[0].attributes.observedOn, "2024-03-01");
|
|
703
703
|
});
|
|
@@ -769,6 +769,38 @@ test("calendar date writes and responses keep leap day across server time zones"
|
|
|
769
769
|
}
|
|
770
770
|
});
|
|
771
771
|
|
|
772
|
+
test("createJsonRestApiHost maps UTC database datetimes to RFC 3339 strings", async () => {
|
|
773
|
+
const fakeKnex = Object.assign(() => {}, {
|
|
774
|
+
client: { config: { client: "sqlite3" } },
|
|
775
|
+
async raw() {
|
|
776
|
+
return [{ version: "3.35.5" }];
|
|
777
|
+
},
|
|
778
|
+
transaction() {}
|
|
779
|
+
});
|
|
780
|
+
const api = await createJsonRestApiHost({ knex: fakeKnex });
|
|
781
|
+
|
|
782
|
+
await api.addResource("jobs", createJsonRestResourceScopeOptions({
|
|
783
|
+
tableName: "jobs",
|
|
784
|
+
schema: {
|
|
785
|
+
id: { type: "id", primary: true },
|
|
786
|
+
scheduledAt: { type: "dateTime" }
|
|
787
|
+
}
|
|
788
|
+
}));
|
|
789
|
+
const record = {
|
|
790
|
+
data: {
|
|
791
|
+
type: "jobs",
|
|
792
|
+
id: "1",
|
|
793
|
+
attributes: {
|
|
794
|
+
scheduledAt: "2024-02-29 12:34:56.123"
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
await api.runHooks("finish", { record });
|
|
800
|
+
|
|
801
|
+
assert.equal(record.data.attributes.scheduledAt, "2024-02-29T12:34:56.123Z");
|
|
802
|
+
});
|
|
803
|
+
|
|
772
804
|
test("returnNullWhenJsonRestResourceMissing only swallows missing-resource errors", async () => {
|
|
773
805
|
await assert.doesNotReject(async () => {
|
|
774
806
|
const result = await returnNullWhenJsonRestResourceMissing(async () => {
|