@genesislcap/mock-server 15.51.1 → 15.53.0
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 +408 -39
- package/dist/db/criteria.d.ts +13 -0
- package/dist/db/criteria.js +60 -21
- package/dist/db/identity.d.ts +11 -0
- package/dist/db/identity.js +46 -0
- package/dist/db/ordering.d.ts +8 -0
- package/dist/db/ordering.js +49 -0
- package/dist/db/schema.d.ts +62 -0
- package/dist/db/schema.js +490 -0
- package/dist/db/store.d.ts +20 -3
- package/dist/db/store.js +300 -50
- package/dist/handlers/commitEvent.js +47 -9
- package/dist/handlers/crudEvents.d.ts +2 -0
- package/dist/handlers/crudEvents.js +138 -0
- package/dist/handlers/dataLogon.js +105 -6
- package/dist/handlers/eventValidation.d.ts +5 -0
- package/dist/handlers/eventValidation.js +29 -0
- package/dist/handlers/jsonSchema.d.ts +4 -2
- package/dist/handlers/jsonSchema.js +222 -61
- package/dist/handlers/meta.d.ts +6 -1
- package/dist/handlers/meta.js +168 -21
- package/dist/handlers/requestReply.js +519 -30
- package/dist/handlers/resourceAuth.d.ts +1 -0
- package/dist/handlers/resourceAuth.js +14 -0
- package/dist/handlers/resources.js +3 -0
- package/dist/index.d.ts +3 -2
- package/dist/index.js +2 -1
- package/dist/protocol/connection.d.ts +2 -0
- package/dist/protocol/errors.d.ts +100 -2
- package/dist/protocol/errors.js +300 -12
- package/dist/protocol/fieldTypes.d.ts +10 -0
- package/dist/protocol/fieldTypes.js +107 -0
- package/dist/protocol/gsfSchemas.d.ts +9 -0
- package/dist/protocol/gsfSchemas.js +423 -0
- package/dist/protocol/messageTypes.d.ts +1 -0
- package/dist/protocol/messageTypes.js +1 -0
- package/dist/protocol/msgNack.d.ts +2 -0
- package/dist/protocol/msgNack.js +10 -0
- package/dist/protocol/rowUpdate.d.ts +5 -3
- package/dist/protocol/rowUpdate.js +33 -15
- package/dist/protocol/schemaValidation.d.ts +11 -0
- package/dist/protocol/schemaValidation.js +186 -0
- package/dist/server.js +95 -29
- package/dist/types.d.ts +45 -0
- package/package.json +1 -1
- package/src/db/criteria.ts +72 -21
- package/src/db/identity.ts +53 -0
- package/src/db/ordering.ts +56 -0
- package/src/db/schema.ts +672 -0
- package/src/db/store.ts +343 -50
- package/src/handlers/commitEvent.ts +54 -10
- package/src/handlers/crudEvents.ts +181 -0
- package/src/handlers/dataLogon.ts +127 -7
- package/src/handlers/eventValidation.ts +43 -0
- package/src/handlers/jsonSchema.ts +302 -69
- package/src/handlers/meta.ts +227 -23
- package/src/handlers/requestReply.ts +626 -33
- package/src/handlers/resourceAuth.ts +18 -0
- package/src/handlers/resources.ts +3 -0
- package/src/index.ts +17 -1
- package/src/protocol/connection.ts +10 -2
- package/src/protocol/errors.ts +361 -13
- package/src/protocol/fieldTypes.ts +115 -0
- package/src/protocol/gsfSchemas.ts +505 -0
- package/src/protocol/messageTypes.ts +1 -0
- package/src/protocol/msgNack.ts +12 -0
- package/src/protocol/rowUpdate.ts +40 -20
- package/src/protocol/schemaValidation.ts +208 -0
- package/src/server.ts +106 -28
- package/src/types.ts +187 -5
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
// The parts of META_REQUEST / JSON_SCHEMA_REQUEST replies that describe the
|
|
2
|
+
// platform's own message classes rather than a project's fields: the same for
|
|
3
|
+
// every event, every dataserver and every request server. Copied from GSF
|
|
4
|
+
// 8.15.29 recordings (test/conformance/recordings/showcase-real/meta-event,
|
|
5
|
+
// json-schema-{event,dataserver,request-server}.json); the conformance suite
|
|
6
|
+
// keeps them in step.
|
|
7
|
+
const MESSAGE_CORE = 'global.genesis.message.core';
|
|
8
|
+
const EVENT_REPLY = `${MESSAGE_CORE}.event.EventReply`;
|
|
9
|
+
const EVENT_ACK = `${EVENT_REPLY}.EventAck`;
|
|
10
|
+
const EVENT_APPROVABLE_ACK = `${EVENT_REPLY}.EventApprovableAck`;
|
|
11
|
+
const EVENT_NACK = `${EVENT_REPLY}.EventNack`;
|
|
12
|
+
const APPROVAL_ENTITY_DETAILS = `${MESSAGE_CORE}.event.ApprovalEntityDetails`;
|
|
13
|
+
const APPROVAL_TYPE = `${MESSAGE_CORE}.event.ApprovalType`;
|
|
14
|
+
const GENESIS_ERROR = `${MESSAGE_CORE}.error.GenesisError`;
|
|
15
|
+
const STANDARD_ERROR = `${MESSAGE_CORE}.error.StandardError`;
|
|
16
|
+
const HTTP_STATUS_CODE = `${MESSAGE_CORE}.HttpStatusCode`;
|
|
17
|
+
const DATA_LOGON_REPLY = `${MESSAGE_CORE}.dataserver.DataLogonReply`;
|
|
18
|
+
const LOGON_ACK = `${DATA_LOGON_REPLY}.LogonAck`;
|
|
19
|
+
const LOGON_NACK = `${DATA_LOGON_REPLY}.LogonNack`;
|
|
20
|
+
const INVALID_INDEX_NACK = `${LOGON_NACK}.InvalidIndexNack`;
|
|
21
|
+
const INVALID_INDEX = `${INVALID_INDEX_NACK}.Index`;
|
|
22
|
+
const QUERY_UPDATE = `${DATA_LOGON_REPLY}.QueryUpdate`;
|
|
23
|
+
export const QUERY_ROW = `${MESSAGE_CORE}.dataserver.QueryRow`;
|
|
24
|
+
const ROW_DETAILS = `${MESSAGE_CORE}.dataserver.RowDetails`;
|
|
25
|
+
const ROW_OPERATION = `${MESSAGE_CORE}.dataserver.RowOperation`;
|
|
26
|
+
const KOTLIN_STRING = 'kotlin.String';
|
|
27
|
+
const KOTLIN_LIST = 'kotlin.collections.List';
|
|
28
|
+
const APPROVAL_TYPES = ['NEW', 'UPDATE', 'REMOVE', 'UNKNOWN'];
|
|
29
|
+
const DEFAULT_APPROVAL_MESSAGE = 'Your request was successful and has been submitted for approval';
|
|
30
|
+
// global.genesis.message.core.HttpStatusCode, in declaration order.
|
|
31
|
+
const HTTP_STATUS_CODES = [
|
|
32
|
+
'100 Continue',
|
|
33
|
+
'101 Switching Protocols',
|
|
34
|
+
'102 Processing',
|
|
35
|
+
'103 Early Hints',
|
|
36
|
+
'200 OK',
|
|
37
|
+
'201 Created',
|
|
38
|
+
'202 Accepted',
|
|
39
|
+
'203 Non-Authoritative Information',
|
|
40
|
+
'204 No Content',
|
|
41
|
+
'205 Reset Content',
|
|
42
|
+
'206 Partial Content',
|
|
43
|
+
'207 Multi-Status',
|
|
44
|
+
'208 Already Reported',
|
|
45
|
+
'226 IM Used',
|
|
46
|
+
'300 Multiple Choice',
|
|
47
|
+
'301 Moved Permanently',
|
|
48
|
+
'302 Found',
|
|
49
|
+
'303 See Other',
|
|
50
|
+
'304 Not Modified',
|
|
51
|
+
'305 Use Proxy',
|
|
52
|
+
'307 Temporary Redirect',
|
|
53
|
+
'308 Permanent Redirect',
|
|
54
|
+
'400 Bad Request',
|
|
55
|
+
'401 Unauthorized',
|
|
56
|
+
'402 Payment Required',
|
|
57
|
+
'403 Forbidden',
|
|
58
|
+
'404 Not Found',
|
|
59
|
+
'405 Method Not Allowed',
|
|
60
|
+
'406 Not Acceptable',
|
|
61
|
+
'407 Proxy Authentication Required',
|
|
62
|
+
'408 Request Timeout',
|
|
63
|
+
'409 Conflict',
|
|
64
|
+
'410 Gone',
|
|
65
|
+
'411 Length Required',
|
|
66
|
+
'412 Precondition Failed',
|
|
67
|
+
'413 Payload Too Large',
|
|
68
|
+
'414 URI Too Long',
|
|
69
|
+
'415 Unsupported Media Type',
|
|
70
|
+
'416 Range Not Satisfiable',
|
|
71
|
+
'417 Expectation Failed',
|
|
72
|
+
'418 I Am A Teapot',
|
|
73
|
+
'418 Enhance Your Calm',
|
|
74
|
+
'421 Misdirected Request',
|
|
75
|
+
'422 Unprocessable Entity',
|
|
76
|
+
'423 Locked',
|
|
77
|
+
'424 Failed Dependency',
|
|
78
|
+
'425 Too Early',
|
|
79
|
+
'426 Upgrade Required',
|
|
80
|
+
'428 Precondition Required',
|
|
81
|
+
'429 Too Many Requests',
|
|
82
|
+
'431 Request Header Fields Too Large',
|
|
83
|
+
'444 No Response',
|
|
84
|
+
'449 RetryWith',
|
|
85
|
+
'450 Blocked By Windows Parental Controls',
|
|
86
|
+
'451 Unavailable For Legal Reasons',
|
|
87
|
+
'499 Client Closed Request',
|
|
88
|
+
'500 Internal Server Error',
|
|
89
|
+
'501 Not Implemented',
|
|
90
|
+
'502 Bad Gateway',
|
|
91
|
+
'503 Service Unavailable',
|
|
92
|
+
'504 Gateway Timeout',
|
|
93
|
+
'505 HTTP Version Not Supported',
|
|
94
|
+
'506 Variant Also Negotiates',
|
|
95
|
+
'507 Insufficient Storage',
|
|
96
|
+
'508 Loop Detected',
|
|
97
|
+
'509 Bandwidth Limit Exceeded',
|
|
98
|
+
'510 Not Extended',
|
|
99
|
+
'511 Network Authentication Required',
|
|
100
|
+
'598 Network Read Timeout Error',
|
|
101
|
+
'599 Network Connection Timeout Error',
|
|
102
|
+
];
|
|
103
|
+
// --- event META: DETAILS.REPLY -----------------------------------------------
|
|
104
|
+
// The META shape of a class property (EventMeta's MetaField), for the reply
|
|
105
|
+
// classes below.
|
|
106
|
+
function metaMessageType(constant) {
|
|
107
|
+
return {
|
|
108
|
+
NAME: 'MESSAGE_TYPE',
|
|
109
|
+
TITLE: 'Message Type',
|
|
110
|
+
DESCRIPTION: 'Message Type',
|
|
111
|
+
NULLABLE: false,
|
|
112
|
+
TYPE: 'STRING',
|
|
113
|
+
JSON_TYPE: 'string',
|
|
114
|
+
OPTIONAL: false,
|
|
115
|
+
READ_ONLY: false,
|
|
116
|
+
CONSTANT: constant,
|
|
117
|
+
GENESIS_TYPE: null,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const META_SOURCE_REF = {
|
|
121
|
+
NAME: 'SOURCE_REF',
|
|
122
|
+
TITLE: 'Source Ref',
|
|
123
|
+
DESCRIPTION: 'Source Ref',
|
|
124
|
+
NULLABLE: false,
|
|
125
|
+
TYPE: 'STRING',
|
|
126
|
+
JSON_TYPE: 'string',
|
|
127
|
+
OPTIONAL: false,
|
|
128
|
+
READ_ONLY: false,
|
|
129
|
+
GENESIS_TYPE: null,
|
|
130
|
+
};
|
|
131
|
+
function metaString(name, optional) {
|
|
132
|
+
return {
|
|
133
|
+
NAME: name,
|
|
134
|
+
DESCRIPTION: KOTLIN_STRING,
|
|
135
|
+
NULLABLE: false,
|
|
136
|
+
TYPE: 'STRING',
|
|
137
|
+
JSON_TYPE: 'string',
|
|
138
|
+
OPTIONAL: optional,
|
|
139
|
+
READ_ONLY: false,
|
|
140
|
+
GENESIS_TYPE: null,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
function metaEnum(name, enumClass, values, optional) {
|
|
144
|
+
return {
|
|
145
|
+
NAME: name,
|
|
146
|
+
DESCRIPTION: enumClass,
|
|
147
|
+
NULLABLE: false,
|
|
148
|
+
VALID_VALUES: values,
|
|
149
|
+
DEFAULT_VALUE: values[0],
|
|
150
|
+
ENUM_CLASS: enumClass,
|
|
151
|
+
TYPE: 'ENUM',
|
|
152
|
+
JSON_TYPE: 'string',
|
|
153
|
+
OPTIONAL: optional,
|
|
154
|
+
READ_ONLY: false,
|
|
155
|
+
...(optional ? { DEFAULT: values[0] } : {}),
|
|
156
|
+
GENESIS_TYPE: null,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function metaList(name, valueType) {
|
|
160
|
+
return {
|
|
161
|
+
NAME: name,
|
|
162
|
+
DESCRIPTION: KOTLIN_LIST,
|
|
163
|
+
NULLABLE: false,
|
|
164
|
+
VALUE_TYPE: valueType,
|
|
165
|
+
UNIQUE: false,
|
|
166
|
+
TYPE: 'ARRAY',
|
|
167
|
+
JSON_TYPE: 'array',
|
|
168
|
+
OPTIONAL: true,
|
|
169
|
+
READ_ONLY: false,
|
|
170
|
+
GENESIS_TYPE: null,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function metaReference(reference) {
|
|
174
|
+
return { NULLABLE: false, REFERENCE: reference, TYPE: 'OBJECT', JSON_TYPE: 'object' };
|
|
175
|
+
}
|
|
176
|
+
function metaClass(name, description, properties) {
|
|
177
|
+
return { NAME: name, DESCRIPTION: description, PROPERTIES: properties };
|
|
178
|
+
}
|
|
179
|
+
// DETAILS.REPLY of every event's META: what EVENT_ACK, EVENT_APPROVABLE_ACK
|
|
180
|
+
// and EVENT_NACK carry. Frozen copies would be shared across replies, so this
|
|
181
|
+
// builds a fresh object each time.
|
|
182
|
+
export function eventReplyMeta() {
|
|
183
|
+
return {
|
|
184
|
+
NAME: 'EVENT_REPLY',
|
|
185
|
+
DESCRIPTION: EVENT_REPLY,
|
|
186
|
+
FIELD: [],
|
|
187
|
+
DEFINITIONS: {
|
|
188
|
+
[EVENT_ACK]: metaClass('EVENT_ACK', EVENT_ACK, [
|
|
189
|
+
metaMessageType('EVENT_ACK'),
|
|
190
|
+
META_SOURCE_REF,
|
|
191
|
+
metaList('GENERATED', {
|
|
192
|
+
NULLABLE: false,
|
|
193
|
+
KEY_TYPE: { NULLABLE: false, TYPE: 'STRING', JSON_TYPE: 'string' },
|
|
194
|
+
VALUE_TYPE: { TYPE: 'ANY', JSON_TYPE: '{}', NULLABLE: true, DEFAULT_VALUE: null },
|
|
195
|
+
TYPE: 'MAP',
|
|
196
|
+
JSON_TYPE: 'object',
|
|
197
|
+
}),
|
|
198
|
+
]),
|
|
199
|
+
[EVENT_APPROVABLE_ACK]: metaClass('EVENT_APPROVABLE_ACK', EVENT_APPROVABLE_ACK, [
|
|
200
|
+
metaMessageType('EVENT_APPROVABLE_ACK'),
|
|
201
|
+
META_SOURCE_REF,
|
|
202
|
+
metaString('ADDITIONAL_DETAILS', true),
|
|
203
|
+
metaString('APPROVAL_MESSAGE', true),
|
|
204
|
+
metaEnum('APPROVAL_TYPE', APPROVAL_TYPE, APPROVAL_TYPES, true),
|
|
205
|
+
metaList('GENERATED', metaReference(APPROVAL_ENTITY_DETAILS)),
|
|
206
|
+
{
|
|
207
|
+
...metaString('DEFAULT_APPROVAL_MESSAGE', true),
|
|
208
|
+
READ_ONLY: true,
|
|
209
|
+
DEFAULT: DEFAULT_APPROVAL_MESSAGE,
|
|
210
|
+
GENESIS_TYPE: null,
|
|
211
|
+
},
|
|
212
|
+
]),
|
|
213
|
+
[APPROVAL_ENTITY_DETAILS]: metaClass('APPROVAL_ENTITY_DETAILS', APPROVAL_ENTITY_DETAILS, [
|
|
214
|
+
metaEnum('APPROVAL_TYPE', APPROVAL_TYPE, APPROVAL_TYPES, false),
|
|
215
|
+
metaString('ENTITY_ID', false),
|
|
216
|
+
metaString('ENTITY_TABLE', false),
|
|
217
|
+
]),
|
|
218
|
+
[EVENT_NACK]: metaClass('EVENT_NACK', EVENT_NACK, [
|
|
219
|
+
metaMessageType('EVENT_NACK'),
|
|
220
|
+
META_SOURCE_REF,
|
|
221
|
+
metaList('ERROR', metaReference(GENESIS_ERROR)),
|
|
222
|
+
metaList('WARNING', metaReference(GENESIS_ERROR)),
|
|
223
|
+
]),
|
|
224
|
+
[GENESIS_ERROR]: metaClass('GENESIS_ERROR', GENESIS_ERROR, [
|
|
225
|
+
metaString('CODE', false),
|
|
226
|
+
metaEnum('STATUS_CODE', HTTP_STATUS_CODE, HTTP_STATUS_CODES, true),
|
|
227
|
+
metaString('TEXT', false),
|
|
228
|
+
]),
|
|
229
|
+
},
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
// --- JSON schemas of the reply classes ---------------------------------------
|
|
233
|
+
const SCHEMA_SOURCE_REF = { type: 'string', title: 'Source Ref', description: 'Source Ref' };
|
|
234
|
+
const SCHEMA_STRING = { type: 'string', description: KOTLIN_STRING };
|
|
235
|
+
const SCHEMA_INT = { type: 'integer', description: 'kotlin.Int' };
|
|
236
|
+
const SCHEMA_BOOLEAN = { type: 'boolean', description: 'kotlin.Boolean' };
|
|
237
|
+
const SCHEMA_NULLABLE_STRING = {
|
|
238
|
+
oneOf: [{ type: 'null' }, { type: 'string' }],
|
|
239
|
+
description: KOTLIN_STRING,
|
|
240
|
+
};
|
|
241
|
+
const ref = (definition) => `#/$defs/${definition}`;
|
|
242
|
+
function schemaList(definition) {
|
|
243
|
+
return {
|
|
244
|
+
type: 'array',
|
|
245
|
+
items: { type: 'object', $ref: ref(definition) },
|
|
246
|
+
description: KOTLIN_LIST,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
function schemaClass(description, properties, required, additionalProperties = false) {
|
|
250
|
+
return { type: 'object', description, properties, additionalProperties, required };
|
|
251
|
+
}
|
|
252
|
+
function errorClass(description) {
|
|
253
|
+
return schemaClass(description, {
|
|
254
|
+
CODE: SCHEMA_STRING,
|
|
255
|
+
STATUS_CODE: { type: 'string', enum: HTTP_STATUS_CODES, description: HTTP_STATUS_CODE },
|
|
256
|
+
TEXT: SCHEMA_STRING,
|
|
257
|
+
}, ['CODE', 'TEXT'], true);
|
|
258
|
+
}
|
|
259
|
+
const ENVELOPE_REQUIRED = ['MESSAGE_TYPE', 'SOURCE_REF'];
|
|
260
|
+
// OUTBOUND of every event's JSON schema: the EventReply union.
|
|
261
|
+
export function eventReplySchema() {
|
|
262
|
+
const approvalType = { type: 'string', enum: APPROVAL_TYPES, description: APPROVAL_TYPE };
|
|
263
|
+
return {
|
|
264
|
+
description: EVENT_REPLY,
|
|
265
|
+
type: 'object',
|
|
266
|
+
oneOf: [
|
|
267
|
+
{ $ref: ref(EVENT_ACK) },
|
|
268
|
+
{ $ref: ref(EVENT_APPROVABLE_ACK) },
|
|
269
|
+
{ $ref: ref(EVENT_NACK) },
|
|
270
|
+
],
|
|
271
|
+
$defs: {
|
|
272
|
+
[EVENT_ACK]: schemaClass(EVENT_ACK, {
|
|
273
|
+
MESSAGE_TYPE: { const: 'EVENT_ACK' },
|
|
274
|
+
SOURCE_REF: SCHEMA_SOURCE_REF,
|
|
275
|
+
GENERATED: { type: 'array', items: { type: 'object' }, description: KOTLIN_LIST },
|
|
276
|
+
}, ENVELOPE_REQUIRED),
|
|
277
|
+
[EVENT_APPROVABLE_ACK]: schemaClass(EVENT_APPROVABLE_ACK, {
|
|
278
|
+
MESSAGE_TYPE: { const: 'EVENT_APPROVABLE_ACK' },
|
|
279
|
+
SOURCE_REF: SCHEMA_SOURCE_REF,
|
|
280
|
+
ADDITIONAL_DETAILS: SCHEMA_STRING,
|
|
281
|
+
APPROVAL_MESSAGE: SCHEMA_STRING,
|
|
282
|
+
APPROVAL_TYPE: approvalType,
|
|
283
|
+
GENERATED: schemaList(APPROVAL_ENTITY_DETAILS),
|
|
284
|
+
DEFAULT_APPROVAL_MESSAGE: {
|
|
285
|
+
type: 'string',
|
|
286
|
+
readOnly: true,
|
|
287
|
+
default: DEFAULT_APPROVAL_MESSAGE,
|
|
288
|
+
description: KOTLIN_STRING,
|
|
289
|
+
},
|
|
290
|
+
}, ENVELOPE_REQUIRED),
|
|
291
|
+
[APPROVAL_ENTITY_DETAILS]: schemaClass(APPROVAL_ENTITY_DETAILS, { APPROVAL_TYPE: approvalType, ENTITY_ID: SCHEMA_STRING, ENTITY_TABLE: SCHEMA_STRING }, ['APPROVAL_TYPE', 'ENTITY_ID', 'ENTITY_TABLE']),
|
|
292
|
+
[EVENT_NACK]: schemaClass(EVENT_NACK, {
|
|
293
|
+
MESSAGE_TYPE: { const: 'EVENT_NACK' },
|
|
294
|
+
SOURCE_REF: SCHEMA_SOURCE_REF,
|
|
295
|
+
ERROR: schemaList(GENESIS_ERROR),
|
|
296
|
+
WARNING: schemaList(GENESIS_ERROR),
|
|
297
|
+
}, ENVELOPE_REQUIRED),
|
|
298
|
+
[GENESIS_ERROR]: errorClass(GENESIS_ERROR),
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
// The envelope properties of an event's INBOUND schema (EventMeta's
|
|
303
|
+
// getMessageJsonSchema), around the event's own DETAILS.
|
|
304
|
+
export function eventInboundProperties(eventName, details) {
|
|
305
|
+
const nullableString = { oneOf: [{ type: 'string' }, { type: 'null' }] };
|
|
306
|
+
return {
|
|
307
|
+
MESSAGE_TYPE: { const: eventName, description: 'Message type', type: 'string' },
|
|
308
|
+
SOURCE_REF: { description: 'Source reference', type: 'string' },
|
|
309
|
+
USER_NAME: { description: 'User name', type: 'string' },
|
|
310
|
+
VALIDATE: { type: 'boolean' },
|
|
311
|
+
IGNORE_WARNINGS: { type: 'boolean' },
|
|
312
|
+
REQUIRES_APPROVAL: { readOnly: true, type: 'boolean' },
|
|
313
|
+
APPROVAL_KEY: nullableString,
|
|
314
|
+
APPROVAL_MESSAGE: nullableString,
|
|
315
|
+
REASON: nullableString,
|
|
316
|
+
SCHEDULABLE: { readOnly: true, const: false, type: 'boolean' },
|
|
317
|
+
DETAILS: details,
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
export const GENESIS_META_SCHEMA = 'https://genesis.global/schema/v1/meta/genesis';
|
|
321
|
+
// INBOUND of a dataserver's JSON schema: the DATA_LOGON message.
|
|
322
|
+
export function dataLogonSchema(queryName) {
|
|
323
|
+
return {
|
|
324
|
+
$schema: 'https://json-schema.org/draft/2019-09/schema',
|
|
325
|
+
description: `GPAL Dataserver definition for ${queryName}`,
|
|
326
|
+
type: 'object',
|
|
327
|
+
properties: {
|
|
328
|
+
MESSAGE_TYPE: { const: 'DATA_LOGON' },
|
|
329
|
+
SOURCE_REF: { type: 'string' },
|
|
330
|
+
USER_NAME: { type: 'string' },
|
|
331
|
+
DETAILS: {
|
|
332
|
+
type: 'object',
|
|
333
|
+
properties: {
|
|
334
|
+
CRITERIA_MATCH: SCHEMA_NULLABLE_STRING,
|
|
335
|
+
DATASOURCE_NAME: { const: queryName },
|
|
336
|
+
FIELDS: SCHEMA_NULLABLE_STRING,
|
|
337
|
+
MAX_ROWS: SCHEMA_INT,
|
|
338
|
+
MAX_VIEW: SCHEMA_INT,
|
|
339
|
+
MOVING_VIEW: SCHEMA_BOOLEAN,
|
|
340
|
+
ORDER_BY: SCHEMA_NULLABLE_STRING,
|
|
341
|
+
REVERSE: SCHEMA_BOOLEAN,
|
|
342
|
+
VIEW_NUMBER: SCHEMA_INT,
|
|
343
|
+
},
|
|
344
|
+
additionalProperties: false,
|
|
345
|
+
required: ['DATASOURCE_NAME'],
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
additionalProperties: false,
|
|
349
|
+
required: ['MESSAGE_TYPE', 'SOURCE_REF', 'USER_NAME', 'DETAILS'],
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
// OUTBOUND of a dataserver's JSON schema: the DataLogonReply union, with the
|
|
353
|
+
// query's own fields in $defs[...QueryRow]. foundation-forms' filters read
|
|
354
|
+
// that definition (filters/filters.ts).
|
|
355
|
+
export function dataLogonReplySchema(rowProperties) {
|
|
356
|
+
return {
|
|
357
|
+
description: DATA_LOGON_REPLY,
|
|
358
|
+
type: 'object',
|
|
359
|
+
oneOf: [
|
|
360
|
+
{ $ref: ref(LOGON_ACK) },
|
|
361
|
+
{ $ref: ref(LOGON_NACK) },
|
|
362
|
+
{ $ref: ref(INVALID_INDEX_NACK) },
|
|
363
|
+
{ $ref: ref(QUERY_UPDATE) },
|
|
364
|
+
],
|
|
365
|
+
$defs: {
|
|
366
|
+
[LOGON_ACK]: schemaClass(LOGON_ACK, {
|
|
367
|
+
MESSAGE_TYPE: { const: 'LOGON_ACK' },
|
|
368
|
+
SOURCE_REF: SCHEMA_SOURCE_REF,
|
|
369
|
+
SEQUENCE_ID: SCHEMA_INT,
|
|
370
|
+
}, ENVELOPE_REQUIRED),
|
|
371
|
+
[LOGON_NACK]: schemaClass(LOGON_NACK, {
|
|
372
|
+
MESSAGE_TYPE: { const: 'LOGON_NACK' },
|
|
373
|
+
SOURCE_REF: SCHEMA_SOURCE_REF,
|
|
374
|
+
ERROR: schemaList(STANDARD_ERROR),
|
|
375
|
+
WARNING: schemaList(STANDARD_ERROR),
|
|
376
|
+
}, ENVELOPE_REQUIRED),
|
|
377
|
+
[STANDARD_ERROR]: errorClass(STANDARD_ERROR),
|
|
378
|
+
[INVALID_INDEX_NACK]: schemaClass(INVALID_INDEX_NACK, {
|
|
379
|
+
MESSAGE_TYPE: { const: 'INVALID_INDEX_NACK' },
|
|
380
|
+
SOURCE_REF: SCHEMA_SOURCE_REF,
|
|
381
|
+
AVAILABLE_INDEXES: schemaList(INVALID_INDEX),
|
|
382
|
+
ERROR: schemaList(STANDARD_ERROR),
|
|
383
|
+
WARNING: schemaList(STANDARD_ERROR),
|
|
384
|
+
}, ['MESSAGE_TYPE', 'SOURCE_REF', 'AVAILABLE_INDEXES', 'ERROR']),
|
|
385
|
+
[INVALID_INDEX]: schemaClass(INVALID_INDEX, {
|
|
386
|
+
FIELDS: { type: 'array', items: { type: 'string' }, description: KOTLIN_LIST },
|
|
387
|
+
NAME: SCHEMA_STRING,
|
|
388
|
+
}, ['FIELDS', 'NAME']),
|
|
389
|
+
[QUERY_UPDATE]: schemaClass(QUERY_UPDATE, {
|
|
390
|
+
MESSAGE_TYPE: { const: 'QUERY_UPDATE' },
|
|
391
|
+
SOURCE_REF: SCHEMA_SOURCE_REF,
|
|
392
|
+
MORE_ROWS: SCHEMA_BOOLEAN,
|
|
393
|
+
ROW: schemaList(QUERY_ROW),
|
|
394
|
+
ROWS_COUNT: { oneOf: [{ type: 'null' }, { type: 'integer' }], description: 'kotlin.Int' },
|
|
395
|
+
SEQUENCE_ID: SCHEMA_INT,
|
|
396
|
+
}, ['MESSAGE_TYPE', 'SOURCE_REF', 'ROW', 'SEQUENCE_ID']),
|
|
397
|
+
[QUERY_ROW]: schemaClass(QUERY_ROW, {
|
|
398
|
+
DETAILS: { type: 'object', $ref: ref(ROW_DETAILS), description: ROW_DETAILS },
|
|
399
|
+
...rowProperties,
|
|
400
|
+
}, ['DETAILS']),
|
|
401
|
+
[ROW_DETAILS]: schemaClass(ROW_DETAILS, {
|
|
402
|
+
OPERATION: {
|
|
403
|
+
type: 'string',
|
|
404
|
+
enum: ['INSERT', 'MODIFY', 'DELETE'],
|
|
405
|
+
description: ROW_OPERATION,
|
|
406
|
+
},
|
|
407
|
+
// Verbatim from GSF 8.15.29, which serializes this one property as a
|
|
408
|
+
// string instead of a schema. Kept so the mock fails wherever the
|
|
409
|
+
// real server does; no client reads it.
|
|
410
|
+
ROW_REF: 'type = string\ndescription = kotlin.String\n',
|
|
411
|
+
}, ['OPERATION', 'ROW_REF']),
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
// The DETAILS a request server accepts besides REQUEST.
|
|
416
|
+
export const REQUEST_DETAILS_SCHEMA = {
|
|
417
|
+
properties: {
|
|
418
|
+
VIEW_NUMBER: { description: 'The page / view to request', type: 'integer' },
|
|
419
|
+
MAX_ROWS: { minimum: 1, description: 'Maximum number of rows to return', type: 'integer' },
|
|
420
|
+
CRITERIA_MATCH: { description: 'Additional criteria for request server', type: 'string' },
|
|
421
|
+
},
|
|
422
|
+
type: 'object',
|
|
423
|
+
};
|
|
@@ -11,6 +11,7 @@ export declare const MESSAGE_TYPE: {
|
|
|
11
11
|
readonly HEARTBEAT_PONG: "HEARTBEAT_PONG";
|
|
12
12
|
readonly DATA_LOGON: "DATA_LOGON";
|
|
13
13
|
readonly LOGON_ACK: "LOGON_ACK";
|
|
14
|
+
readonly LOGON_NACK: "LOGON_NACK";
|
|
14
15
|
readonly DATA_LOGOFF: "DATA_LOGOFF";
|
|
15
16
|
readonly LOGOFF_ACK: "LOGOFF_ACK";
|
|
16
17
|
readonly QUERY_UPDATE: "QUERY_UPDATE";
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { Row } from '../types.ts';
|
|
2
|
+
import type { NackErrorEntry } from './errors.ts';
|
|
3
|
+
export declare function serviceMsgNack(errors: NackErrorEntry[], warnings?: NackErrorEntry[]): Row;
|
|
2
4
|
export declare function resourceNotMappedNack(resourceName: string | undefined): Row;
|
|
3
5
|
export declare function nonExistentSubscriptionNack(): Row;
|
|
4
6
|
export declare function subscriptionAlreadyExistsNack(): Row;
|
package/dist/protocol/msgNack.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import { MESSAGE_TYPE } from "./messageTypes.js";
|
|
2
|
+
// A service's own MSG_NACK (genesis-messages MsgNack): how a request server
|
|
3
|
+
// answers a request it fails, GSF 8.15.x. Unlike the router's frame below, its
|
|
4
|
+
// items are GenesisErrors (@type, a string CODE, TEXT, STATUS_CODE), there is
|
|
5
|
+
// a WARNING list and no DETAILS. Recorded: a criteria-only REQ_ without
|
|
6
|
+
// REQUEST answers {WARNING: [], ERROR: [{'@type': 'StandardError', CODE:
|
|
7
|
+
// 'INTERNAL_ERROR', TEXT: 'Unexpected server error', STATUS_CODE: '500
|
|
8
|
+
// Internal Server Error'}], MESSAGE_TYPE: 'MSG_NACK'}.
|
|
9
|
+
export function serviceMsgNack(errors, warnings = []) {
|
|
10
|
+
return { WARNING: warnings, ERROR: errors, MESSAGE_TYPE: MESSAGE_TYPE.MSG_NACK };
|
|
11
|
+
}
|
|
2
12
|
const HTTP_NOT_FOUND = 404;
|
|
3
13
|
const HTTP_CONFLICT = 409;
|
|
4
14
|
// The real router's rejection frame (genesis-router MsgType.Outbound.Error,
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { Store } from '../db/store.ts';
|
|
2
|
+
import type { MockServerConfig, Row } from '../types.ts';
|
|
2
3
|
export type RowOperation = 'INSERT' | 'MODIFY' | 'DELETE';
|
|
3
|
-
export declare function stampRow(row: Row, rowRef: unknown, operation: RowOperation): Row;
|
|
4
|
-
export declare function
|
|
4
|
+
export declare function stampRow(row: Row, rowRef: unknown, operation: RowOperation, legacy?: boolean): Row;
|
|
5
|
+
export declare function rowRefFor(config: MockServerConfig, store: Store, resourceName: string, row: Row): unknown;
|
|
6
|
+
export declare function dataserverRow(projected: Row, source: Row, fields: string[] | undefined): Row;
|
|
@@ -1,17 +1,35 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
};
|
|
1
|
+
import { RECORD_FIELDS, withoutRecordFields } from "../db/identity.js";
|
|
2
|
+
import { isLegacyFidelity } from "./fidelity.js";
|
|
3
|
+
// Stamps a row with DETAILS.OPERATION/ROW_REF, the only fields the client's
|
|
4
|
+
// dataServerResultFilter uses to distinguish INSERT/MODIFY/DELETE on a
|
|
5
|
+
// QUERY_UPDATE (both for the snapshot pages and later unsolicited pushes on
|
|
6
|
+
// the same SOURCE_REF — the envelope is otherwise identical). OPERATION must
|
|
7
|
+
// be exactly 'INSERT' | 'MODIFY' | 'DELETE'; anything else is silently
|
|
8
|
+
// dropped client-side. GSF puts ROW_REF in DETAILS only; clients copy it onto
|
|
9
|
+
// the row themselves (`row.DETAILS[rowId] ?? row[rowId]`). Legacy mode also
|
|
10
|
+
// set it on the row itself, as this engine did up to 15.47.
|
|
11
|
+
export function stampRow(row, rowRef, operation, legacy = false) {
|
|
12
|
+
const DETAILS = { OPERATION: operation, ROW_REF: rowRef };
|
|
13
|
+
return legacy ? { ...row, ROW_REF: rowRef, DETAILS } : { ...row, DETAILS };
|
|
14
14
|
}
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
// DETAILS.ROW_REF for a row of a dataserver's source: the record's RECORD_ID
|
|
16
|
+
// as a string, as GSF sends it (a view row's comes from its base row). Legacy
|
|
17
|
+
// mode used the pk value, or the '|'-joined composite key. Read per call, so
|
|
18
|
+
// flipping `fidelity` on a running server applies to the next push.
|
|
19
|
+
export function rowRefFor(config, store, resourceName, row) {
|
|
20
|
+
return isLegacyFidelity(config)
|
|
21
|
+
? store.getRowRef(resourceName, row)
|
|
22
|
+
: store.getRecordRef(resourceName, row);
|
|
23
|
+
}
|
|
24
|
+
// RECORD_ID and TIMESTAMP on a dataserver row: the real dataserver never
|
|
25
|
+
// sends them unless the query's fields block names them. `projected` is the
|
|
26
|
+
// row already narrowed to the query's columns; `source` the stored row the
|
|
27
|
+
// values come from.
|
|
28
|
+
export function dataserverRow(projected, source, fields) {
|
|
29
|
+
const row = { ...withoutRecordFields(projected) };
|
|
30
|
+
for (const field of RECORD_FIELDS) {
|
|
31
|
+
if (fields?.includes(field) && source[field] !== undefined)
|
|
32
|
+
row[field] = source[field];
|
|
33
|
+
}
|
|
34
|
+
return row;
|
|
17
35
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { NackErrorInput } from './errors.ts';
|
|
2
|
+
type Schema = Record<string, any>;
|
|
3
|
+
export interface SchemaError {
|
|
4
|
+
keyword: string;
|
|
5
|
+
instanceLocation: string;
|
|
6
|
+
property?: string;
|
|
7
|
+
message: string;
|
|
8
|
+
}
|
|
9
|
+
export declare function validateSchema(schema: Schema, value: unknown, location?: string): SchemaError[];
|
|
10
|
+
export declare function schemaFieldErrors(errors: SchemaError[]): NackErrorInput[];
|
|
11
|
+
export {};
|