@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.
Files changed (70) hide show
  1. package/README.md +408 -39
  2. package/dist/db/criteria.d.ts +13 -0
  3. package/dist/db/criteria.js +60 -21
  4. package/dist/db/identity.d.ts +11 -0
  5. package/dist/db/identity.js +46 -0
  6. package/dist/db/ordering.d.ts +8 -0
  7. package/dist/db/ordering.js +49 -0
  8. package/dist/db/schema.d.ts +62 -0
  9. package/dist/db/schema.js +490 -0
  10. package/dist/db/store.d.ts +20 -3
  11. package/dist/db/store.js +300 -50
  12. package/dist/handlers/commitEvent.js +47 -9
  13. package/dist/handlers/crudEvents.d.ts +2 -0
  14. package/dist/handlers/crudEvents.js +138 -0
  15. package/dist/handlers/dataLogon.js +105 -6
  16. package/dist/handlers/eventValidation.d.ts +5 -0
  17. package/dist/handlers/eventValidation.js +29 -0
  18. package/dist/handlers/jsonSchema.d.ts +4 -2
  19. package/dist/handlers/jsonSchema.js +222 -61
  20. package/dist/handlers/meta.d.ts +6 -1
  21. package/dist/handlers/meta.js +168 -21
  22. package/dist/handlers/requestReply.js +519 -30
  23. package/dist/handlers/resourceAuth.d.ts +1 -0
  24. package/dist/handlers/resourceAuth.js +14 -0
  25. package/dist/handlers/resources.js +3 -0
  26. package/dist/index.d.ts +3 -2
  27. package/dist/index.js +2 -1
  28. package/dist/protocol/connection.d.ts +2 -0
  29. package/dist/protocol/errors.d.ts +100 -2
  30. package/dist/protocol/errors.js +300 -12
  31. package/dist/protocol/fieldTypes.d.ts +10 -0
  32. package/dist/protocol/fieldTypes.js +107 -0
  33. package/dist/protocol/gsfSchemas.d.ts +9 -0
  34. package/dist/protocol/gsfSchemas.js +423 -0
  35. package/dist/protocol/messageTypes.d.ts +1 -0
  36. package/dist/protocol/messageTypes.js +1 -0
  37. package/dist/protocol/msgNack.d.ts +2 -0
  38. package/dist/protocol/msgNack.js +10 -0
  39. package/dist/protocol/rowUpdate.d.ts +5 -3
  40. package/dist/protocol/rowUpdate.js +33 -15
  41. package/dist/protocol/schemaValidation.d.ts +11 -0
  42. package/dist/protocol/schemaValidation.js +186 -0
  43. package/dist/server.js +95 -29
  44. package/dist/types.d.ts +45 -0
  45. package/package.json +1 -1
  46. package/src/db/criteria.ts +72 -21
  47. package/src/db/identity.ts +53 -0
  48. package/src/db/ordering.ts +56 -0
  49. package/src/db/schema.ts +672 -0
  50. package/src/db/store.ts +343 -50
  51. package/src/handlers/commitEvent.ts +54 -10
  52. package/src/handlers/crudEvents.ts +181 -0
  53. package/src/handlers/dataLogon.ts +127 -7
  54. package/src/handlers/eventValidation.ts +43 -0
  55. package/src/handlers/jsonSchema.ts +302 -69
  56. package/src/handlers/meta.ts +227 -23
  57. package/src/handlers/requestReply.ts +626 -33
  58. package/src/handlers/resourceAuth.ts +18 -0
  59. package/src/handlers/resources.ts +3 -0
  60. package/src/index.ts +17 -1
  61. package/src/protocol/connection.ts +10 -2
  62. package/src/protocol/errors.ts +361 -13
  63. package/src/protocol/fieldTypes.ts +115 -0
  64. package/src/protocol/gsfSchemas.ts +505 -0
  65. package/src/protocol/messageTypes.ts +1 -0
  66. package/src/protocol/msgNack.ts +12 -0
  67. package/src/protocol/rowUpdate.ts +40 -20
  68. package/src/protocol/schemaValidation.ts +208 -0
  69. package/src/server.ts +106 -28
  70. package/src/types.ts +187 -5
@@ -1,10 +1,38 @@
1
- import { deriveFields } from '../db/metadata.ts';
1
+ import {
2
+ eventDefaultOf,
3
+ eventFields,
4
+ knownValues,
5
+ querySchema,
6
+ requestFieldsOf,
7
+ requestReplyReplySchema,
8
+ requestReplySchema,
9
+ requestReplySourceSchema,
10
+ resolveEventTarget,
11
+ type EventTarget,
12
+ type ResolvedField,
13
+ } from '../db/schema.ts';
2
14
  import type { Store } from '../db/store.ts';
3
15
  import type { Connection } from '../protocol/connection.ts';
4
16
  import { isLegacyFidelity } from '../protocol/fidelity.ts';
17
+ import {
18
+ enumClassOf,
19
+ eventMetaTypeOf,
20
+ jsonTypeOf,
21
+ jvmClassOf,
22
+ rangeOf,
23
+ titleCase,
24
+ } from '../protocol/fieldTypes.ts';
25
+ import { eventReplyMeta } from '../protocol/gsfSchemas.ts';
5
26
  import { ERROR_CODE, MESSAGE_TYPE } from '../protocol/messageTypes.ts';
6
27
  import { resourceNotMappedNack } from '../protocol/msgNack.ts';
7
- import type { GenesisMessage, MockServerConfig, Row } from '../types.ts';
28
+ import type {
29
+ FieldMetadata,
30
+ GenesisMessage,
31
+ MockServerConfig,
32
+ QueryDef,
33
+ RequestReplyDef,
34
+ Row,
35
+ } from '../types.ts';
8
36
  import { hiddenFields, requestingUser } from './resourceAuth.ts';
9
37
 
10
38
  // createMetaRequestMessage (foundation-comms/src/connect/message.ts) puts the
@@ -16,13 +44,161 @@ function resolveResourceName(message: GenesisMessage): string {
16
44
  );
17
45
  }
18
46
 
47
+ // A FIELD / REQUEST_FIELD / REPLY_FIELD entry: VALID_VALUES on ENUM fields
48
+ // only (DataServerMetaData.kt, ReqRepMetaData.kt).
49
+ function fieldMetadata(field: ResolvedField): FieldMetadata {
50
+ return {
51
+ NAME: field.name,
52
+ TYPE: field.type,
53
+ ...(field.type === 'ENUM' && knownValues(field) ? { VALID_VALUES: knownValues(field) } : {}),
54
+ };
55
+ }
56
+
57
+ // Index-less queries report the unnamed RECORD_ID index the real dataserver
58
+ // adds (QueryDefinition.DEFAULT_RECORD_ID_INDEX).
59
+ const DEFAULT_INDEXES = [{ NAME: null, FIELDS: 'RECORD_ID' }];
60
+
61
+ function dataserverMeta(
62
+ name: string,
63
+ query: QueryDef,
64
+ config: MockServerConfig,
65
+ store: Store,
66
+ hidden: Set<string>,
67
+ ): Row {
68
+ const fields = querySchema(query, config, store).fields.filter(
69
+ (field) => !hidden.has(field.name),
70
+ );
71
+ return {
72
+ TYPE: 'DATASERVER',
73
+ NAME: name,
74
+ FIELD: fields.map(fieldMetadata),
75
+ INDEXES: query.indexes?.length
76
+ ? query.indexes.map((index) => ({ NAME: index.name, FIELDS: index.fields.join(' ') }))
77
+ : DEFAULT_INDEXES,
78
+ };
79
+ }
80
+
81
+ // The two request-server shapes of ReqRepMetaData.kt: criteria-only
82
+ // (REQUEST_FIELD: [], SORTABLE_FIELDS, CRITERIA_FIELDS, no INDICES) and plain
83
+ // (REQUEST_FIELD from the request block, INDICES: [],
84
+ // CRITERIA_ONLY_REQUEST: false). REPLY_FIELD is the reply block, else the
85
+ // source's fields then RECORD_ID and TIMESTAMP, for either
86
+ // (requestReplyReplySchema). SORTABLE_FIELDS and CRITERIA_FIELDS are the
87
+ // entity's fields whatever the reply block says (the dictionary entry's
88
+ // nativeQueryFields, RequestReplyServer.kt).
89
+ function requestServerMeta(
90
+ name: string,
91
+ requestReply: RequestReplyDef,
92
+ config: MockServerConfig,
93
+ store: Store,
94
+ hidden: Set<string>,
95
+ ): Row {
96
+ const visible = (field: ResolvedField) => !hidden.has(field.name);
97
+ const replyFields = requestReplyReplySchema(requestReply, config, store).fields.filter(visible);
98
+ const entityFields = requestReplySourceSchema(requestReply, config, store).fields;
99
+ if (requestReply.criteriaOnly) {
100
+ const names = entityFields.filter(visible).map((field) => field.name);
101
+ return {
102
+ TYPE: 'REQUEST_SERVER',
103
+ REQUEST_FIELD: [],
104
+ CRITERIA_ONLY_REQUEST: true,
105
+ SORTABLE_FIELDS: names,
106
+ CRITERIA_FIELDS: names,
107
+ REPLY_FIELD: replyFields.map(fieldMetadata),
108
+ NAME: name,
109
+ };
110
+ }
111
+ const requestFields = requestFieldsOf(requestReply, store).map(
112
+ (fieldName) =>
113
+ entityFields.find((field) => field.name === fieldName) ?? {
114
+ name: fieldName,
115
+ type: 'STRING',
116
+ nullable: false,
117
+ generated: false,
118
+ },
119
+ );
120
+ return {
121
+ TYPE: 'REQUEST_SERVER',
122
+ REQUEST_FIELD: requestFields.map(fieldMetadata),
123
+ INDICES: [],
124
+ CRITERIA_ONLY_REQUEST: false,
125
+ REPLY_FIELD: replyFields.map(fieldMetadata),
126
+ NAME: name,
127
+ };
128
+ }
129
+
130
+ // One FIELD entry of an event's META (EventMeta's MetaField, flattened as the
131
+ // real server serializes it). Only a table event's class (a generated DAO)
132
+ // carries the dictionary's annotations: TITLE, MIN_LENGTH / MAX_LENGTH,
133
+ // MIN / MAX and GENESIS_TYPE. A delete's <Table>.ById and a custom DTO have
134
+ // none of them (GENESIS_TYPE null). An ENUM always names its values, its
135
+ // class and a DEFAULT_VALUE (the class's first value when the field has no
136
+ // default); DEFAULT appears only when the field has a default.
137
+ export function eventMetaField(
138
+ field: ResolvedField,
139
+ { annotated, appName }: { annotated: boolean; appName?: string },
140
+ ): Row {
141
+ const type = field.type;
142
+ const enumClass = () => enumClassOf(field.table, field.name, appName);
143
+ const values = knownValues(field);
144
+ const fieldDefault = eventDefaultOf(field);
145
+ const meta: Row = { NAME: field.name };
146
+ if (annotated) meta.TITLE = field.title ?? titleCase(field.name);
147
+ meta.DESCRIPTION = field.description ?? jvmClassOf(type, enumClass);
148
+ meta.NULLABLE = field.nullable;
149
+ if (annotated && type === 'STRING') {
150
+ if (field.minLength !== undefined) meta.MIN_LENGTH = field.minLength;
151
+ if (field.maxSize !== undefined) meta.MAX_LENGTH = field.maxSize;
152
+ }
153
+ const range = annotated ? rangeOf(type) : undefined;
154
+ if (range) [meta.MIN, meta.MAX] = range;
155
+ if (type === 'ENUM') {
156
+ // The enum class's own default: the declared one, else its first value.
157
+ if (values) meta.VALID_VALUES = values;
158
+ const typeDefault = field.default ?? values?.[0];
159
+ if (typeDefault !== undefined) meta.DEFAULT_VALUE = typeDefault;
160
+ meta.ENUM_CLASS = enumClass();
161
+ }
162
+ meta.TYPE = eventMetaTypeOf(type);
163
+ meta.JSON_TYPE = jsonTypeOf(type);
164
+ if (type !== 'ENUM' && field.default !== undefined) meta.DEFAULT_VALUE = field.default;
165
+ // OPTIONAL is about the payload, resolved by eventFields (schema.ts).
166
+ meta.OPTIONAL = field.optional ?? false;
167
+ meta.READ_ONLY = false;
168
+ if (fieldDefault !== undefined) meta.DEFAULT = fieldDefault;
169
+ meta.GENESIS_TYPE = annotated ? type : null;
170
+ return meta;
171
+ }
172
+
173
+ function eventMeta(
174
+ name: string,
175
+ target: EventTarget,
176
+ fields: ResolvedField[],
177
+ appName: string | undefined,
178
+ ): Row {
179
+ const annotated = target.kind === 'table';
180
+ return {
181
+ NAME: name,
182
+ ...(target.title ? { TITLE: target.title } : {}),
183
+ DESCRIPTION: target.className,
184
+ FIELD: fields.map((field) => eventMetaField(field, { annotated, appName })),
185
+ DEFINITIONS: {},
186
+ REPLY: eventReplyMeta(),
187
+ REQUIRES_APPROVAL: false,
188
+ SCHEDULABLE: false,
189
+ TYPE: 'EVENT_HANDLER',
190
+ };
191
+ }
192
+
19
193
  // Connect.getMetadata reads metaResponse.DETAILS (not the top-level message)
20
194
  // and extractFieldDefinitions (metadata.utils.ts) branches on DETAILS.TYPE:
21
195
  // DATASERVER resources need DETAILS.FIELD, REQUEST_SERVER ones need
22
196
  // DETAILS.REPLY_FIELD. Get either wrong and every grid silently reports
23
- // "Metadata Fetch failed" / "Unsupported resource type: undefined".
24
- // The real reply is FEATURE_ACK (getMetadata reads only DETAILS, so the type
25
- // is for fidelity; legacy mode echoed META_REQUEST).
197
+ // "Metadata Fetch failed" / "Unsupported resource type: undefined". Event
198
+ // META is what the AI assistant builds its write tools from
199
+ // (ai-assistant/src/genesis/event-fields.ts): no event META, no write tools.
200
+ // Request servers answer by resource name (TRADE); REQ_TRADE is not a
201
+ // resource name and gets the router's 404, as on the real server.
26
202
  export function handleMetaRequest(
27
203
  message: GenesisMessage,
28
204
  connection: Connection,
@@ -32,9 +208,11 @@ export function handleMetaRequest(
32
208
  const resourceName = resolveResourceName(message);
33
209
  const query = config.queries?.[resourceName];
34
210
  const requestReply = config.requestReplies?.[resourceName];
35
-
36
211
  const legacy = isLegacyFidelity(config);
37
- if (!query && !requestReply) {
212
+ const event =
213
+ query || requestReply || legacy ? undefined : resolveEventTarget(resourceName, config);
214
+
215
+ if (!query && !requestReply && !event) {
38
216
  connection.reply(
39
217
  message,
40
218
  legacy
@@ -49,21 +227,47 @@ export function handleMetaRequest(
49
227
  return;
50
228
  }
51
229
 
52
- const resolved = (query ?? requestReply)!;
53
- // Resolver-only request-replies have no backing table/view — their field
54
- // metadata comes from the def's own metadataRows sample (plus fieldTypes).
55
- const rows: Row[] = resolved.source
56
- ? store.getMetadataRows(resolved.source)
57
- : (requestReply?.metadataRows ?? []);
58
- // Hidden fields disappear from the metadata too — a column the user can't
59
- // see must not be offered to the grid at all.
60
- const hidden = hiddenFields(resolved.auth, requestingUser(connection));
61
- const fields = deriveFields(rows, resolved.fieldTypes).filter((field) => !hidden.has(field.NAME));
230
+ if (legacy) {
231
+ connection.reply(message, {
232
+ MESSAGE_TYPE: message.MESSAGE_TYPE,
233
+ DETAILS: legacyMeta(resourceName, query, requestReply, connection, config, store),
234
+ });
235
+ return;
236
+ }
237
+
238
+ let details: Row;
239
+ if (event) {
240
+ details = eventMeta(resourceName, event, eventFields(event, config, store), config.appName);
241
+ } else {
242
+ // Hidden fields disappear from the metadata too — a column the user can't
243
+ // see must not be offered to the grid at all.
244
+ const hidden = hiddenFields((query ?? requestReply)!.auth, requestingUser(connection));
245
+ details = query
246
+ ? dataserverMeta(resourceName, query, config, store, hidden)
247
+ : requestServerMeta(resourceName, requestReply!, config, store, hidden);
248
+ }
249
+ connection.reply(message, { MESSAGE_TYPE: MESSAGE_TYPE.FEATURE_ACK, DETAILS: details });
250
+ }
62
251
 
63
- connection.reply(message, {
64
- MESSAGE_TYPE: legacy ? message.MESSAGE_TYPE : MESSAGE_TYPE.FEATURE_ACK,
65
- DETAILS: query
66
- ? { NAME: resourceName, TYPE: 'DATASERVER', FIELD: fields, KEY: [] }
67
- : { NAME: resourceName, TYPE: 'REQUEST_SERVER', REPLY_FIELD: fields },
68
- });
252
+ // What this engine answered up to 15.47: {NAME, TYPE} fields only, KEY: []
253
+ // on dataservers, REPLY_FIELD alone on request servers, and no event META.
254
+ // Declared types (and query fields blocks) still apply.
255
+ function legacyMeta(
256
+ resourceName: string,
257
+ query: QueryDef | undefined,
258
+ requestReply: RequestReplyDef | undefined,
259
+ connection: Connection,
260
+ config: MockServerConfig,
261
+ store: Store,
262
+ ): Row {
263
+ const hidden = hiddenFields((query ?? requestReply)!.auth, requestingUser(connection));
264
+ const schema = query
265
+ ? querySchema(query, config, store)
266
+ : requestReplySchema(requestReply!, config, store);
267
+ const fields = schema.fields
268
+ .filter((field) => !hidden.has(field.name))
269
+ .map((field) => ({ NAME: field.name, TYPE: field.type }));
270
+ return query
271
+ ? { NAME: resourceName, TYPE: 'DATASERVER', FIELD: fields, KEY: [] }
272
+ : { NAME: resourceName, TYPE: 'REQUEST_SERVER', REPLY_FIELD: fields };
69
273
  }