@getaster/data 0.0.0-stage → 0.1.0-preview.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 (84) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +10 -0
  3. package/README.md +6 -2
  4. package/dist/authentication.d.ts +42 -0
  5. package/dist/authentication.js +121 -0
  6. package/dist/changes/changeset.d.ts +47 -0
  7. package/dist/changes/changeset.js +75 -0
  8. package/dist/collection/aggregate.d.ts +22 -0
  9. package/dist/collection/aggregate.js +94 -0
  10. package/dist/collection/collection.d.ts +96 -0
  11. package/dist/collection/collection.js +588 -0
  12. package/dist/collection/grouping.d.ts +75 -0
  13. package/dist/collection/grouping.js +190 -0
  14. package/dist/collection/groups.d.ts +31 -0
  15. package/dist/collection/groups.js +117 -0
  16. package/dist/collection/summary.d.ts +17 -0
  17. package/dist/collection/summary.js +103 -0
  18. package/dist/composition/activity.d.ts +36 -0
  19. package/dist/composition/activity.js +60 -0
  20. package/dist/composition/command.d.ts +22 -0
  21. package/dist/composition/command.js +57 -0
  22. package/dist/composition/lifetime.d.ts +11 -0
  23. package/dist/composition/lifetime.js +33 -0
  24. package/dist/composition/sessions.d.ts +64 -0
  25. package/dist/composition/sessions.js +209 -0
  26. package/dist/context.d.ts +76 -0
  27. package/dist/context.js +211 -0
  28. package/dist/criteria/ast.d.ts +79 -0
  29. package/dist/criteria/ast.js +1 -0
  30. package/dist/criteria/compile.d.ts +37 -0
  31. package/dist/criteria/compile.js +233 -0
  32. package/dist/criteria/evaluate.d.ts +20 -0
  33. package/dist/criteria/evaluate.js +171 -0
  34. package/dist/criteria/filter-draft.d.ts +79 -0
  35. package/dist/criteria/filter-draft.js +320 -0
  36. package/dist/criteria/relative.d.ts +13 -0
  37. package/dist/criteria/relative.js +82 -0
  38. package/dist/criteria/values.d.ts +20 -0
  39. package/dist/criteria/values.js +283 -0
  40. package/dist/edit/edit-model.d.ts +152 -0
  41. package/dist/edit/edit-model.js +620 -0
  42. package/dist/edit/list-edit.d.ts +116 -0
  43. package/dist/edit/list-edit.js +332 -0
  44. package/dist/edit/record-edit.d.ts +93 -0
  45. package/dist/edit/record-edit.js +317 -0
  46. package/dist/export/csv.d.ts +25 -0
  47. package/dist/export/csv.js +75 -0
  48. package/dist/index.d.ts +38 -0
  49. package/dist/index.js +35 -0
  50. package/dist/odata/errors.d.ts +25 -0
  51. package/dist/odata/errors.js +56 -0
  52. package/dist/odata/source.d.ts +65 -0
  53. package/dist/odata/source.js +193 -0
  54. package/dist/progress/status.d.ts +29 -0
  55. package/dist/progress/status.js +105 -0
  56. package/dist/schema/csdl.d.ts +13 -0
  57. package/dist/schema/csdl.js +531 -0
  58. package/dist/schema/model.d.ts +132 -0
  59. package/dist/schema/model.js +1 -0
  60. package/dist/scope/filters.d.ts +130 -0
  61. package/dist/scope/filters.js +349 -0
  62. package/dist/scope/publications.d.ts +27 -0
  63. package/dist/scope/publications.js +74 -0
  64. package/dist/scope/related-aggregate.d.ts +9 -0
  65. package/dist/scope/related-aggregate.js +67 -0
  66. package/dist/scope/scope.d.ts +181 -0
  67. package/dist/scope/scope.js +574 -0
  68. package/dist/selection/selection.d.ts +56 -0
  69. package/dist/selection/selection.js +137 -0
  70. package/dist/store/keys.d.ts +10 -0
  71. package/dist/store/keys.js +21 -0
  72. package/dist/store/store.d.ts +72 -0
  73. package/dist/store/store.js +143 -0
  74. package/dist/template/template.d.ts +19 -0
  75. package/dist/template/template.js +186 -0
  76. package/dist/transport.d.ts +28 -0
  77. package/dist/transport.js +20 -0
  78. package/dist/tree/tree.d.ts +117 -0
  79. package/dist/tree/tree.js +435 -0
  80. package/dist/validate/criteria-rule.d.ts +28 -0
  81. package/dist/validate/criteria-rule.js +22 -0
  82. package/dist/validate/validate.d.ts +13 -0
  83. package/dist/validate/validate.js +123 -0
  84. package/package.json +32 -4
@@ -0,0 +1,531 @@
1
+ /** A metadata problem, with the JSON path of the offending member. */
2
+ export class SchemaError extends Error {
3
+ path;
4
+ constructor(message, path) {
5
+ super(`${path}: ${message}`);
6
+ this.name = "SchemaError";
7
+ this.path = path;
8
+ }
9
+ }
10
+ const EDM_TYPES = new Set([
11
+ "Edm.String",
12
+ "Edm.Boolean",
13
+ "Edm.Byte",
14
+ "Edm.SByte",
15
+ "Edm.Int16",
16
+ "Edm.Int32",
17
+ "Edm.Int64",
18
+ "Edm.Single",
19
+ "Edm.Double",
20
+ "Edm.Decimal",
21
+ "Edm.Date",
22
+ "Edm.TimeOfDay",
23
+ "Edm.DateTimeOffset",
24
+ "Edm.Duration",
25
+ "Edm.Guid",
26
+ "Edm.Binary",
27
+ ]);
28
+ class FrozenMap extends Map {
29
+ #sealed = false;
30
+ seal() {
31
+ this.#sealed = true;
32
+ return this;
33
+ }
34
+ set(key, value) {
35
+ if (this.#sealed)
36
+ throw new TypeError("schema is immutable");
37
+ return super.set(key, value);
38
+ }
39
+ delete(_key) {
40
+ throw new TypeError("schema is immutable");
41
+ }
42
+ clear() {
43
+ throw new TypeError("schema is immutable");
44
+ }
45
+ }
46
+ class FrozenSet extends Set {
47
+ #sealed = false;
48
+ seal() {
49
+ this.#sealed = true;
50
+ return this;
51
+ }
52
+ add(value) {
53
+ if (this.#sealed)
54
+ throw new TypeError("schema is immutable");
55
+ return super.add(value);
56
+ }
57
+ delete(_value) {
58
+ throw new TypeError("schema is immutable");
59
+ }
60
+ clear() {
61
+ throw new TypeError("schema is immutable");
62
+ }
63
+ }
64
+ function frozenSet(items) {
65
+ const s = new FrozenSet();
66
+ for (const i of items)
67
+ s.add(i);
68
+ return s.seal();
69
+ }
70
+ function isObject(v) {
71
+ return typeof v === "object" && v !== null && !Array.isArray(v);
72
+ }
73
+ function obj(v, path) {
74
+ if (!isObject(v))
75
+ throw new SchemaError("expected an object", path);
76
+ return v;
77
+ }
78
+ function optBool(o, key, path) {
79
+ const v = o[key];
80
+ if (v === undefined)
81
+ return undefined;
82
+ if (typeof v !== "boolean")
83
+ throw new SchemaError("expected a boolean", `${path}/${key}`);
84
+ return v;
85
+ }
86
+ function optInt(o, key, path) {
87
+ const v = o[key];
88
+ if (v === undefined)
89
+ return undefined;
90
+ if (typeof v !== "number" || !Number.isInteger(v) || v < 0) {
91
+ throw new SchemaError("expected a non-negative integer", `${path}/${key}`);
92
+ }
93
+ return v;
94
+ }
95
+ /** Maps a vocabulary namespace or alias to the alias it is known by here. */
96
+ function namespaceResolver(root) {
97
+ const map = new Map();
98
+ const refs = root["$Reference"];
99
+ if (refs !== undefined) {
100
+ for (const [uri, ref] of Object.entries(obj(refs, "/$Reference"))) {
101
+ const includes = obj(ref, `/$Reference/${uri}`)["$Include"];
102
+ if (includes === undefined)
103
+ continue;
104
+ if (!Array.isArray(includes)) {
105
+ throw new SchemaError("expected an array", `/$Reference/${uri}/$Include`);
106
+ }
107
+ includes.forEach((inc, i) => {
108
+ const p = `/$Reference/${uri}/$Include/${i}`;
109
+ const o = obj(inc, p);
110
+ const ns = o["$Namespace"];
111
+ if (typeof ns !== "string")
112
+ throw new SchemaError("expected a string", `${p}/$Namespace`);
113
+ const alias = typeof o["$Alias"] === "string" ? o["$Alias"] : ns;
114
+ map.set(ns, alias);
115
+ map.set(alias, alias);
116
+ });
117
+ }
118
+ }
119
+ return (ns) => map.get(ns) ?? ns;
120
+ }
121
+ /** `@Org.OData.Core.V1.Description#q` becomes `Core.Description#q`; `@A@B` chains. */
122
+ function annotationKey(raw, resolve) {
123
+ return raw
124
+ .split("@")
125
+ .filter((s) => s !== "")
126
+ .map((seg) => {
127
+ const hash = seg.indexOf("#");
128
+ const term = hash < 0 ? seg : seg.slice(0, hash);
129
+ const qualifier = hash < 0 ? "" : seg.slice(hash);
130
+ const dot = term.lastIndexOf(".");
131
+ if (dot < 0)
132
+ return seg;
133
+ return `${resolve(term.slice(0, dot))}${term.slice(dot)}${qualifier}`;
134
+ })
135
+ .join("@");
136
+ }
137
+ function annotationsOf(o, resolve) {
138
+ const out = {};
139
+ for (const [k, v] of Object.entries(o)) {
140
+ if (k.startsWith("@"))
141
+ out[annotationKey(k, resolve)] = v;
142
+ }
143
+ return out;
144
+ }
145
+ function str(v) {
146
+ return typeof v === "string" ? v : undefined;
147
+ }
148
+ function num(v) {
149
+ return typeof v === "number" ? v : undefined;
150
+ }
151
+ function toValue(v, path) {
152
+ if (v === null || typeof v === "string" || typeof v === "number" || typeof v === "boolean") {
153
+ return v;
154
+ }
155
+ throw new SchemaError("expected a primitive value", path);
156
+ }
157
+ function propertyPaths(v) {
158
+ if (!Array.isArray(v))
159
+ return [];
160
+ const out = [];
161
+ for (const item of v) {
162
+ if (typeof item === "string")
163
+ out.push(item);
164
+ else if (isObject(item) && typeof item["$PropertyPath"] === "string") {
165
+ out.push(item["$PropertyPath"]);
166
+ }
167
+ }
168
+ return out;
169
+ }
170
+ /**
171
+ * Parses a CSDL JSON 4.01 `$metadata` document. Throws `SchemaError` when
172
+ * the document is malformed; a property it can't model (a stream, an
173
+ * untyped or complex value, a collection) is left out and listed in
174
+ * `skipped`, and the rest of the schema stands.
175
+ */
176
+ export function parseCsdl(json) {
177
+ const root = obj(json, "");
178
+ const resolve = namespaceResolver(root);
179
+ const skipped = [];
180
+ const namespaces = Object.keys(root).filter((k) => !k.startsWith("$") && !k.startsWith("@"));
181
+ if (namespaces.length === 0)
182
+ throw new SchemaError("no schema namespace", "");
183
+ /** Alias or namespace to namespace. */
184
+ const byAlias = new Map();
185
+ for (const ns of namespaces) {
186
+ byAlias.set(ns, ns);
187
+ const alias = str(obj(root[ns], `/${ns}`)["$Alias"]);
188
+ if (alias !== undefined)
189
+ byAlias.set(alias, ns);
190
+ }
191
+ const qualify = (name, path) => {
192
+ const dot = name.lastIndexOf(".");
193
+ const ns = dot < 0 ? undefined : byAlias.get(name.slice(0, dot));
194
+ if (ns === undefined)
195
+ throw new SchemaError(`type '${name}' is not in this document`, path);
196
+ return `${ns}.${name.slice(dot + 1)}`;
197
+ };
198
+ const containerRef = root["$EntityContainer"];
199
+ if (typeof containerRef !== "string") {
200
+ throw new SchemaError("missing $EntityContainer", "/$EntityContainer");
201
+ }
202
+ const containerQualified = qualify(containerRef, "/$EntityContainer");
203
+ const namespace = containerQualified.slice(0, containerQualified.lastIndexOf("."));
204
+ const containerName = containerQualified.slice(namespace.length + 1);
205
+ const typeDrafts = new Map();
206
+ let containerJson;
207
+ for (const ns of namespaces) {
208
+ for (const [name, member] of Object.entries(obj(root[ns], `/${ns}`))) {
209
+ if (name.startsWith("$") || name.startsWith("@"))
210
+ continue;
211
+ const p = `/${ns}/${name}`;
212
+ const o = obj(member, p);
213
+ if (o["$Kind"] === "EntityType")
214
+ typeDrafts.set(`${ns}.${name}`, { qualified: `${ns}.${name}`, name, namespace: ns, path: p, json: o });
215
+ else if (o["$Kind"] === "EntityContainer" && ns === namespace && name === containerName)
216
+ containerJson = o;
217
+ }
218
+ }
219
+ if (!containerJson) {
220
+ throw new SchemaError(`entity container '${containerRef}' not found`, "/$EntityContainer");
221
+ }
222
+ /** A type's own members, then its base type's: a derived type has every property its base has. */
223
+ const chain = (draft) => {
224
+ const out = [];
225
+ for (let d = draft; d;) {
226
+ if (out.includes(d))
227
+ throw new SchemaError("$BaseType cycle", `${draft.path}/$BaseType`);
228
+ out.push(d);
229
+ const base = d.json["$BaseType"];
230
+ if (base === undefined)
231
+ break;
232
+ if (typeof base !== "string")
233
+ throw new SchemaError("expected a string", `${d.path}/$BaseType`);
234
+ const next = typeDrafts.get(qualify(base, `${d.path}/$BaseType`));
235
+ if (!next)
236
+ throw new SchemaError(`unknown base type '${base}'`, `${d.path}/$BaseType`);
237
+ d = next;
238
+ }
239
+ return out;
240
+ };
241
+ const fieldsByType = new Map();
242
+ const keysByType = new Map();
243
+ const navsDraftByType = new Map();
244
+ const typeAnnotations = new Map();
245
+ for (const [qualified, draft] of typeDrafts) {
246
+ const fields = [];
247
+ const navs = [];
248
+ let rawKey;
249
+ let keyPath = "";
250
+ const annotations = {};
251
+ for (const d of chain(draft).reverse()) {
252
+ Object.assign(annotations, annotationsOf(d.json, resolve));
253
+ if (d.json["$Key"] !== undefined)
254
+ [rawKey, keyPath] = [d.json["$Key"], `${d.path}/$Key`];
255
+ for (const [name, member] of Object.entries(d.json)) {
256
+ if (name.startsWith("$") || name.startsWith("@"))
257
+ continue;
258
+ const p = `${d.path}/${name}`;
259
+ const o = obj(member, p);
260
+ if (o["$Kind"] === "NavigationProperty") {
261
+ const t = o["$Type"];
262
+ if (typeof t !== "string")
263
+ throw new SchemaError("missing $Type", `${p}/$Type`);
264
+ navs.push({ typeName: qualified, name, path: p, raw: o, targetType: qualify(t, `${p}/$Type`) });
265
+ continue;
266
+ }
267
+ const reason = unmodelled(o);
268
+ if (reason)
269
+ skipped.push({ path: p, reason });
270
+ else
271
+ fields.push(parseField(name, o, p, resolve));
272
+ }
273
+ }
274
+ let key = [];
275
+ if (rawKey !== undefined) {
276
+ if (!Array.isArray(rawKey))
277
+ throw new SchemaError("expected an array", keyPath);
278
+ key = rawKey.map((k, i) => {
279
+ if (typeof k !== "string") {
280
+ throw new SchemaError("only simple key property names are supported", `${keyPath}/${i}`);
281
+ }
282
+ if (!fields.some((f) => f.name === k)) {
283
+ throw new SchemaError(`key property '${k}' is not a property`, `${keyPath}/${i}`);
284
+ }
285
+ return k;
286
+ });
287
+ }
288
+ fieldsByType.set(qualified, fields);
289
+ keysByType.set(qualified, key);
290
+ navsDraftByType.set(qualified, navs);
291
+ typeAnnotations.set(qualified, annotations);
292
+ }
293
+ const setDrafts = [];
294
+ for (const [name, member] of Object.entries(containerJson)) {
295
+ if (name.startsWith("$") || name.startsWith("@"))
296
+ continue;
297
+ const p = `/${namespace}/${containerName}/${name}`;
298
+ const o = obj(member, p);
299
+ if (o["$Collection"] !== true || typeof o["$Type"] !== "string")
300
+ continue;
301
+ const typeName = qualify(o["$Type"], `${p}/$Type`);
302
+ if (!typeDrafts.has(typeName)) {
303
+ throw new SchemaError(`unknown entity type '${o["$Type"]}'`, `${p}/$Type`);
304
+ }
305
+ const bindings = new Map();
306
+ const b = o["$NavigationPropertyBinding"];
307
+ if (b !== undefined) {
308
+ for (const [navPath, target] of Object.entries(obj(b, `${p}/$NavigationPropertyBinding`))) {
309
+ if (typeof target !== "string") {
310
+ throw new SchemaError("expected a string", `${p}/$NavigationPropertyBinding/${navPath}`);
311
+ }
312
+ bindings.set(navPath, target);
313
+ }
314
+ }
315
+ setDrafts.push({
316
+ name,
317
+ typeName,
318
+ path: p,
319
+ raw: o,
320
+ bindings,
321
+ annotations: annotationsOf(o, resolve),
322
+ });
323
+ }
324
+ const setNames = new Set(setDrafts.map((s) => s.name));
325
+ // Pass 2: navigations.
326
+ const navsByType = new Map();
327
+ for (const [qualified, navDrafts] of navsDraftByType) {
328
+ const list = [];
329
+ for (const nd of navDrafts) {
330
+ const o = nd.raw;
331
+ const collection = o["$Collection"] === true;
332
+ const targetDraft = typeDrafts.get(nd.targetType);
333
+ if (!targetDraft) {
334
+ throw new SchemaError(`unknown entity type '${nd.targetType}'`, `${nd.path}/$Type`);
335
+ }
336
+ let target;
337
+ for (const s of setDrafts) {
338
+ if (s.typeName !== nd.typeName)
339
+ continue;
340
+ const t = s.bindings.get(nd.name);
341
+ if (t === undefined)
342
+ continue;
343
+ if (!setNames.has(t)) {
344
+ throw new SchemaError(`binding targets unknown entity set '${t}'`, `${s.path}/$NavigationPropertyBinding/${nd.name}`);
345
+ }
346
+ target = t;
347
+ break;
348
+ }
349
+ if (target === undefined) {
350
+ const candidates = setDrafts.filter((s) => s.typeName === nd.targetType);
351
+ if (candidates.length > 1) {
352
+ throw new SchemaError("no navigation binding and several entity sets of the target type", nd.path);
353
+ }
354
+ // Contained targets have no set of their own.
355
+ target = candidates[0]?.name ?? targetDraft.name;
356
+ }
357
+ const own = constraintPairs(o["$ReferentialConstraint"], `${nd.path}/$ReferentialConstraint`);
358
+ let keys = own;
359
+ const partner = str(o["$Partner"]);
360
+ if (own.length === 0 && collection && partner !== undefined) {
361
+ const partnerJson = chain(targetDraft).map((d) => d.json[partner]).find(isObject);
362
+ if (partnerJson) {
363
+ keys = constraintPairs(partnerJson["$ReferentialConstraint"], `${targetDraft.path}/${partner}/$ReferentialConstraint`).map(([dependent, principal]) => [principal, dependent]);
364
+ }
365
+ }
366
+ const nav = {
367
+ name: nd.name,
368
+ target,
369
+ collection,
370
+ keys: keys.map((k) => Object.freeze(k)),
371
+ ...(partner !== undefined ? { partner } : {}),
372
+ owned: annotationsOf(o, resolve)["AsterData.Owned"] === true,
373
+ };
374
+ list.push(Object.freeze(nav));
375
+ }
376
+ navsByType.set(qualified, list);
377
+ }
378
+ const types = new FrozenMap();
379
+ for (const [qualified, draft] of typeDrafts) {
380
+ const fields = Object.freeze(fieldsByType.get(qualified));
381
+ const navigations = Object.freeze(navsByType.get(qualified));
382
+ const fieldMap = new Map(fields.map((f) => [f.name, f]));
383
+ const navMap = new Map(navigations.map((n) => [n.name, n]));
384
+ const ann = typeAnnotations.get(qualified);
385
+ const title = str(ann["Core.Description"]);
386
+ const pluralTitle = str(ann["AsterData.PluralTitle"]);
387
+ const titleField = str(ann["AsterData.TitleField"]);
388
+ const type = {
389
+ name: draft.name,
390
+ qualifiedName: qualified,
391
+ key: Object.freeze([...keysByType.get(qualified)]),
392
+ fields,
393
+ navigations,
394
+ ...(title !== undefined ? { title } : {}),
395
+ ...(pluralTitle !== undefined ? { pluralTitle } : {}),
396
+ ...(titleField !== undefined ? { titleField } : {}),
397
+ annotations: Object.freeze(ann),
398
+ field: (n) => fieldMap.get(n),
399
+ navigation: (n) => navMap.get(n),
400
+ };
401
+ types.set(qualified, Object.freeze(type));
402
+ }
403
+ const sets = new FrozenMap();
404
+ for (const sd of setDrafts) {
405
+ const type = types.get(sd.typeName);
406
+ const oc = sd.annotations["Core.OptimisticConcurrency"];
407
+ const filterable = recordMember(sd.annotations, "Capabilities.FilterRestrictions", "NonFilterableProperties");
408
+ const sortable = recordMember(sd.annotations, "Capabilities.SortRestrictions", "NonSortableProperties");
409
+ const t = sd.annotations["AsterData.CountThreshold"];
410
+ const threshold = typeof t === "number" || typeof t === "string" ? Number(t) : Number.NaN;
411
+ sets.set(sd.name, Object.freeze({
412
+ name: sd.name,
413
+ type,
414
+ etag: Array.isArray(oc),
415
+ nonFilterable: frozenSet(propertyPaths(filterable)),
416
+ nonSortable: frozenSet(propertyPaths(sortable)),
417
+ ...(Number.isFinite(threshold) && threshold >= 0 ? { countThreshold: threshold } : {}),
418
+ annotations: Object.freeze(sd.annotations),
419
+ }));
420
+ }
421
+ types.seal();
422
+ sets.seal();
423
+ return Object.freeze({
424
+ namespace,
425
+ sets,
426
+ types,
427
+ skipped: Object.freeze(skipped),
428
+ set(name) {
429
+ const s = sets.get(name);
430
+ if (!s)
431
+ throw new SchemaError(`no entity set '${name}'`, `/${containerName}`);
432
+ return s;
433
+ },
434
+ });
435
+ }
436
+ /** Why a property can't be modelled here, or undefined if it can. */
437
+ function unmodelled(o) {
438
+ const type = o["$Type"] ?? "Edm.String";
439
+ if (o["$Collection"] === true)
440
+ return "a collection-valued property";
441
+ if (typeof type !== "string")
442
+ return "a property without a type name";
443
+ switch (type) {
444
+ case "Edm.Stream":
445
+ return "a stream";
446
+ case "Edm.Untyped":
447
+ return "an untyped value";
448
+ default:
449
+ return EDM_TYPES.has(type) ? undefined : `type ${type}`;
450
+ }
451
+ }
452
+ /** A member of a record-valued annotation, or the flattened `Term.Member` key. */
453
+ function recordMember(ann, term, member) {
454
+ const rec = ann[term];
455
+ if (isObject(rec) && rec[member] !== undefined)
456
+ return rec[member];
457
+ return ann[`${term}.${member}`];
458
+ }
459
+ function constraintPairs(v, path) {
460
+ if (v === undefined)
461
+ return [];
462
+ const o = obj(v, path);
463
+ return Object.entries(o).map(([dependent, principal]) => {
464
+ if (typeof principal !== "string")
465
+ throw new SchemaError("expected a string", `${path}/${dependent}`);
466
+ return [dependent, principal];
467
+ });
468
+ }
469
+ function parseField(name, o, path, resolve) {
470
+ const type = o["$Type"] ?? "Edm.String";
471
+ const ann = annotationsOf(o, resolve);
472
+ const maxLength = optInt(o, "$MaxLength", path);
473
+ const precision = optInt(o, "$Precision", path);
474
+ const scale = optInt(o, "$Scale", path);
475
+ const title = str(ann["Core.Description"]);
476
+ const prompt = str(ann["Core.LongDescription"]);
477
+ const pattern = str(ann["Validation.Pattern"]);
478
+ const minLength = num(ann["AsterData.MinLength"]);
479
+ let allowedValues;
480
+ const av = ann["Validation.AllowedValues"];
481
+ if (av !== undefined) {
482
+ const ap = `${path}/@Validation.AllowedValues`;
483
+ if (!Array.isArray(av))
484
+ throw new SchemaError("expected an array", ap);
485
+ allowedValues = av.map((item, i) => {
486
+ const io = obj(item, `${ap}/${i}`);
487
+ const label = str(annotationsOf(io, resolve)["Core.Description"]);
488
+ return Object.freeze({
489
+ value: toValue(io["Value"], `${ap}/${i}/Value`),
490
+ ...(label !== undefined ? { label } : {}),
491
+ });
492
+ });
493
+ }
494
+ const bound = (v, term) => {
495
+ if (v === undefined)
496
+ return undefined;
497
+ if (typeof v !== "number" && typeof v !== "string") {
498
+ throw new SchemaError("expected a number or string", `${path}/@${term}`);
499
+ }
500
+ return v;
501
+ };
502
+ const minimum = bound(ann["Validation.Minimum"], "Validation.Minimum");
503
+ const maximum = bound(ann["Validation.Maximum"], "Validation.Maximum");
504
+ const exMin = ann["Validation.Minimum@Validation.Exclusive"];
505
+ const exMax = ann["Validation.Maximum@Validation.Exclusive"];
506
+ const defaultValue = o["$DefaultValue"] === undefined ? undefined : toValue(o["$DefaultValue"], `${path}/$DefaultValue`);
507
+ const rootValue = ann["AsterData.RootValue"] === undefined ? undefined : toValue(ann["AsterData.RootValue"], `${path}/@AsterData.RootValue`);
508
+ return Object.freeze({
509
+ name,
510
+ type: type,
511
+ nullable: optBool(o, "$Nullable", path) ?? false,
512
+ ...(maxLength !== undefined ? { maxLength } : {}),
513
+ ...(precision !== undefined ? { precision } : {}),
514
+ ...(scale !== undefined ? { scale } : {}),
515
+ ...(title !== undefined ? { title } : {}),
516
+ ...(prompt !== undefined ? { prompt } : {}),
517
+ computed: ann["Core.Computed"] === true,
518
+ immutable: ann["Core.Immutable"] === true,
519
+ ...(allowedValues !== undefined ? { allowedValues: Object.freeze(allowedValues) } : {}),
520
+ ...(pattern !== undefined ? { pattern } : {}),
521
+ ...(minimum !== undefined ? { minimum } : {}),
522
+ ...(maximum !== undefined ? { maximum } : {}),
523
+ ...(exMin === true ? { exclusiveMinimum: true } : {}),
524
+ ...(exMax === true ? { exclusiveMaximum: true } : {}),
525
+ ...(minLength !== undefined ? { minLength } : {}),
526
+ ...(ann["AsterData.EmptyAsNull"] === true ? { emptyAsNull: true } : {}),
527
+ ...(defaultValue !== undefined ? { defaultValue } : {}),
528
+ ...(rootValue !== undefined ? { rootValue } : {}),
529
+ annotations: Object.freeze(ann),
530
+ });
531
+ }
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Built only from CSDL JSON `$metadata` (`csdl.ts`).
3
+ *
4
+ * Values stay in their OData JSON form (`IEEE754Compatible=true`) so nothing
5
+ * loses precision:
6
+ *
7
+ * | Edm type | JS value |
8
+ * |----------------------------------|----------------------------------|
9
+ * | String, Guid, Duration | `string` |
10
+ * | Boolean | `boolean` |
11
+ * | Byte, SByte, Int16, Int32 | `number` |
12
+ * | Single, Double | `number` |
13
+ * | Int64, Decimal | `string` (exact decimal text) |
14
+ * | Date | `string` `YYYY-MM-DD` |
15
+ * | TimeOfDay | `string` `hh:mm:ss[.fff]` |
16
+ * | DateTimeOffset | `string` RFC 3339 with offset |
17
+ * | Binary | `string` base64url |
18
+ * | null | `null` |
19
+ */
20
+ export type EdmType = "Edm.String" | "Edm.Boolean" | "Edm.Byte" | "Edm.SByte" | "Edm.Int16" | "Edm.Int32" | "Edm.Int64" | "Edm.Single" | "Edm.Double" | "Edm.Decimal" | "Edm.Date" | "Edm.TimeOfDay" | "Edm.DateTimeOffset" | "Edm.Duration" | "Edm.Guid" | "Edm.Binary";
21
+ export type Value = string | number | boolean | null;
22
+ /** A record as the data core holds it: property name to value. */
23
+ export type Entity = Readonly<Record<string, Value>>;
24
+ /**
25
+ * Annotations by qualified term name, with namespace aliases resolved to the
26
+ * alias the vocabulary is known by here: `Core.Description`,
27
+ * `Validation.Minimum`, `AsterData.DisplayField`.
28
+ */
29
+ export type Annotations = Readonly<Record<string, unknown>>;
30
+ export interface AllowedValue {
31
+ readonly value: Value;
32
+ /** `Core.Description` on the allowed value: the label to show. */
33
+ readonly label?: string;
34
+ }
35
+ export interface Field {
36
+ readonly name: string;
37
+ readonly type: EdmType;
38
+ readonly nullable: boolean;
39
+ readonly maxLength?: number;
40
+ readonly precision?: number;
41
+ readonly scale?: number;
42
+ /** `Core.Description`. */
43
+ readonly title?: string;
44
+ /** `Core.LongDescription`. */
45
+ readonly prompt?: string;
46
+ /** `Core.Computed`: the server assigns it. */
47
+ readonly computed: boolean;
48
+ /** `Core.Immutable`: settable on create only. */
49
+ readonly immutable: boolean;
50
+ /** `Validation.AllowedValues`. */
51
+ readonly allowedValues?: readonly AllowedValue[];
52
+ /** `Validation.Pattern`. */
53
+ readonly pattern?: string;
54
+ /** `Validation.Minimum` / `Validation.Maximum`, as written in metadata. */
55
+ readonly minimum?: number | string;
56
+ readonly maximum?: number | string;
57
+ /** `Validation.Exclusive` on the minimum or maximum. */
58
+ readonly exclusiveMinimum?: boolean;
59
+ readonly exclusiveMaximum?: boolean;
60
+ /** `AsterData.MinLength`. */
61
+ readonly minLength?: number;
62
+ /** `AsterData.EmptyAsNull`: empty text entered becomes null. */
63
+ readonly emptyAsNull?: boolean;
64
+ /** `$DefaultValue`: what the server stores when a create leaves the field out. */
65
+ readonly defaultValue?: Value;
66
+ /** `AsterData.RootValue`, on a parent field: the value a root node holds. */
67
+ readonly rootValue?: Value;
68
+ readonly annotations: Annotations;
69
+ }
70
+ export interface Navigation {
71
+ readonly name: string;
72
+ /** The entity set the navigation leads to. */
73
+ readonly target: string;
74
+ readonly collection: boolean;
75
+ /** `[property of this type, property of the target]` pairs. */
76
+ readonly keys: readonly (readonly [string, string])[];
77
+ readonly partner?: string;
78
+ /**
79
+ * `AsterData.Owned`: children can't exist without the parent, and are
80
+ * saved with it through deep insert and deep update with `@delta`.
81
+ */
82
+ readonly owned: boolean;
83
+ }
84
+ export interface EntityType {
85
+ readonly name: string;
86
+ readonly qualifiedName: string;
87
+ /** Key property names, in key order. */
88
+ readonly key: readonly string[];
89
+ /** In declaration order. */
90
+ readonly fields: readonly Field[];
91
+ readonly navigations: readonly Navigation[];
92
+ /** `Core.Description` on the type. */
93
+ readonly title?: string;
94
+ /** `AsterData.PluralTitle`. */
95
+ readonly pluralTitle?: string;
96
+ /** `AsterData.TitleField`. */
97
+ readonly titleField?: string;
98
+ readonly annotations: Annotations;
99
+ field(name: string): Field | undefined;
100
+ navigation(name: string): Navigation | undefined;
101
+ }
102
+ export interface EntitySet {
103
+ readonly name: string;
104
+ readonly type: EntityType;
105
+ /** `Core.OptimisticConcurrency` is present: writes need `If-Match`. */
106
+ readonly etag: boolean;
107
+ /** `Capabilities.FilterRestrictions/NonFilterableProperties`. */
108
+ readonly nonFilterable: ReadonlySet<string>;
109
+ /** `Capabilities.SortRestrictions/NonSortableProperties`. */
110
+ readonly nonSortable: ReadonlySet<string>;
111
+ /**
112
+ * `AsterData.CountThreshold`: below this many results the server's count
113
+ * is exact; at or above it, it's estimated or left out unless a
114
+ * collection asks for `exactCount`.
115
+ */
116
+ readonly countThreshold?: number;
117
+ readonly annotations: Annotations;
118
+ }
119
+ /** A part of `$metadata` the client can't model and left out. */
120
+ export interface Skipped {
121
+ readonly path: string;
122
+ readonly reason: string;
123
+ }
124
+ export interface Schema {
125
+ /** The namespace of the entity container. */
126
+ readonly namespace: string;
127
+ readonly sets: ReadonlyMap<string, EntitySet>;
128
+ readonly types: ReadonlyMap<string, EntityType>;
129
+ readonly skipped: readonly Skipped[];
130
+ /** Throws if the set doesn't exist. */
131
+ set(name: string): EntitySet;
132
+ }
@@ -0,0 +1 @@
1
+ export {};