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