@lovrozagar/oat 0.1.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 (145) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +401 -0
  3. package/bin/oat.js +2 -0
  4. package/dist/cli.js +386 -0
  5. package/dist/config/define-config.d.ts +165 -0
  6. package/dist/config/define-config.d.ts.map +1 -0
  7. package/dist/config/define-config.js +24 -0
  8. package/dist/config/define-config.js.map +1 -0
  9. package/dist/config/load.d.ts +10 -0
  10. package/dist/config/load.d.ts.map +1 -0
  11. package/dist/config/load.js +61 -0
  12. package/dist/config/load.js.map +1 -0
  13. package/dist/index.d.ts +17 -0
  14. package/dist/index.js +9 -0
  15. package/dist/reference/defects.d.ts +254 -0
  16. package/dist/reference/defects.d.ts.map +1 -0
  17. package/dist/reference/defects.js +265 -0
  18. package/dist/reference/defects.js.map +1 -0
  19. package/dist/reference/dialect.d.ts +104 -0
  20. package/dist/reference/dialect.d.ts.map +1 -0
  21. package/dist/reference/dialect.js +174 -0
  22. package/dist/reference/dialect.js.map +1 -0
  23. package/dist/reference/http.d.ts +79 -0
  24. package/dist/reference/http.d.ts.map +1 -0
  25. package/dist/reference/http.js +889 -0
  26. package/dist/reference/http.js.map +1 -0
  27. package/dist/reference/model.d.ts +56 -0
  28. package/dist/reference/model.d.ts.map +1 -0
  29. package/dist/reference/model.js +156 -0
  30. package/dist/reference/model.js.map +1 -0
  31. package/dist/reference/query.d.ts +45 -0
  32. package/dist/reference/query.d.ts.map +1 -0
  33. package/dist/reference/query.js +353 -0
  34. package/dist/reference/query.js.map +1 -0
  35. package/dist/reference/spec.d.ts +13 -0
  36. package/dist/reference/spec.d.ts.map +1 -0
  37. package/dist/reference/spec.js +547 -0
  38. package/dist/reference/spec.js.map +1 -0
  39. package/dist/reference/store-api.d.ts +59 -0
  40. package/dist/reference/store-api.d.ts.map +1 -0
  41. package/dist/reference/store-api.js +76 -0
  42. package/dist/reference/store-api.js.map +1 -0
  43. package/dist/reference/stores/memory.d.ts +27 -0
  44. package/dist/reference/stores/memory.d.ts.map +1 -0
  45. package/dist/reference/stores/memory.js +97 -0
  46. package/dist/reference/stores/memory.js.map +1 -0
  47. package/dist/reference/stores/postgres.d.ts +61 -0
  48. package/dist/reference/stores/postgres.d.ts.map +1 -0
  49. package/dist/reference/stores/postgres.js +406 -0
  50. package/dist/reference/stores/postgres.js.map +1 -0
  51. package/dist/reference/stores/sqlite-driver.d.ts +54 -0
  52. package/dist/reference/stores/sqlite-driver.d.ts.map +1 -0
  53. package/dist/reference/stores/sqlite-driver.js +99 -0
  54. package/dist/reference/stores/sqlite-driver.js.map +1 -0
  55. package/dist/reference/stores/sqlite.d.ts +68 -0
  56. package/dist/reference/stores/sqlite.d.ts.map +1 -0
  57. package/dist/reference/stores/sqlite.js +464 -0
  58. package/dist/reference/stores/sqlite.js.map +1 -0
  59. package/dist/report/console.d.ts +33 -0
  60. package/dist/report/console.d.ts.map +1 -0
  61. package/dist/report/console.js +237 -0
  62. package/dist/report/console.js.map +1 -0
  63. package/dist/report/matrix.d.ts +131 -0
  64. package/dist/report/matrix.d.ts.map +1 -0
  65. package/dist/report/matrix.js +865 -0
  66. package/dist/report/matrix.js.map +1 -0
  67. package/dist/report/render.d.ts +66 -0
  68. package/dist/report/render.d.ts.map +1 -0
  69. package/dist/report/render.js +423 -0
  70. package/dist/report/render.js.map +1 -0
  71. package/dist/runtime/async.d.ts +34 -0
  72. package/dist/runtime/async.d.ts.map +1 -0
  73. package/dist/runtime/async.js +130 -0
  74. package/dist/runtime/async.js.map +1 -0
  75. package/dist/runtime/auth.d.ts +46 -0
  76. package/dist/runtime/auth.d.ts.map +1 -0
  77. package/dist/runtime/auth.js +236 -0
  78. package/dist/runtime/auth.js.map +1 -0
  79. package/dist/runtime/checks.d.ts +89 -0
  80. package/dist/runtime/checks.d.ts.map +1 -0
  81. package/dist/runtime/checks.js +3400 -0
  82. package/dist/runtime/checks.js.map +1 -0
  83. package/dist/runtime/client.d.ts +65 -0
  84. package/dist/runtime/client.d.ts.map +1 -0
  85. package/dist/runtime/client.js +122 -0
  86. package/dist/runtime/client.js.map +1 -0
  87. package/dist/runtime/finding.d.ts +50 -0
  88. package/dist/runtime/finding.d.ts.map +1 -0
  89. package/dist/runtime/finding.js +50 -0
  90. package/dist/runtime/finding.js.map +1 -0
  91. package/dist/runtime/fixture.d.ts +20 -0
  92. package/dist/runtime/fixture.d.ts.map +1 -0
  93. package/dist/runtime/fixture.js +175 -0
  94. package/dist/runtime/fixture.js.map +1 -0
  95. package/dist/runtime/progress.d.ts +41 -0
  96. package/dist/runtime/progress.d.ts.map +1 -0
  97. package/dist/runtime/progress.js +186 -0
  98. package/dist/runtime/progress.js.map +1 -0
  99. package/dist/runtime/run.d.ts +69 -0
  100. package/dist/runtime/run.d.ts.map +1 -0
  101. package/dist/runtime/run.js +575 -0
  102. package/dist/runtime/run.js.map +1 -0
  103. package/dist/runtime/teardown.d.ts +38 -0
  104. package/dist/runtime/teardown.d.ts.map +1 -0
  105. package/dist/runtime/teardown.js +129 -0
  106. package/dist/runtime/teardown.js.map +1 -0
  107. package/dist/runtime/validate.d.ts +31 -0
  108. package/dist/runtime/validate.d.ts.map +1 -0
  109. package/dist/runtime/validate.js +111 -0
  110. package/dist/runtime/validate.js.map +1 -0
  111. package/dist/runtime/world.d.ts +49 -0
  112. package/dist/runtime/world.d.ts.map +1 -0
  113. package/dist/runtime/world.js +119 -0
  114. package/dist/runtime/world.js.map +1 -0
  115. package/dist/spec/collection.d.ts +31 -0
  116. package/dist/spec/collection.d.ts.map +1 -0
  117. package/dist/spec/collection.js +121 -0
  118. package/dist/spec/collection.js.map +1 -0
  119. package/dist/spec/conventions.d.ts +103 -0
  120. package/dist/spec/conventions.d.ts.map +1 -0
  121. package/dist/spec/conventions.js +275 -0
  122. package/dist/spec/conventions.js.map +1 -0
  123. package/dist/spec/extensions.d.ts +74 -0
  124. package/dist/spec/extensions.d.ts.map +1 -0
  125. package/dist/spec/extensions.js +335 -0
  126. package/dist/spec/extensions.js.map +1 -0
  127. package/dist/spec/graph.d.ts +89 -0
  128. package/dist/spec/graph.d.ts.map +1 -0
  129. package/dist/spec/graph.js +318 -0
  130. package/dist/spec/graph.js.map +1 -0
  131. package/dist/spec/load.d.ts +36 -0
  132. package/dist/spec/load.d.ts.map +1 -0
  133. package/dist/spec/load.js +166 -0
  134. package/dist/spec/load.js.map +1 -0
  135. package/dist/spec/types.d.ts +57 -0
  136. package/dist/spec/types.d.ts.map +1 -0
  137. package/dist/spec/types.js +24 -0
  138. package/dist/spec/types.js.map +1 -0
  139. package/labs/README.md +130 -0
  140. package/labs/annotated-openapi.yaml +311 -0
  141. package/labs/anyrow.config.ts +107 -0
  142. package/labs/demo.mjs +124 -0
  143. package/labs/local.config.ts +41 -0
  144. package/labs/minimal.config.ts +32 -0
  145. package/package.json +80 -0
@@ -0,0 +1,103 @@
1
+ /**
2
+ * What a list endpoint's query parameters and response envelope *mean*, derived from the
3
+ * document rather than assumed.
4
+ *
5
+ * Checks used to match on literal names — `"filter"`, `"page"`, `envelope.hasMore` — which made
6
+ * roughly half of them fire only against APIs that happened to share the fixture's spelling.
7
+ * Everywhere else they were skipped, and skipped silently, so a quiet run on an unfamiliar API
8
+ * read exactly like a clean one.
9
+ *
10
+ * Names are matched against the aliases real APIs actually use, and the shape of the declared
11
+ * schema breaks ties. Anything still ambiguous is left `undefined` — the check then reports that
12
+ * it did not apply, and `x-query` can state the answer outright.
13
+ */
14
+ import type { CollectionShape } from "./collection.ts";
15
+ import type { ParameterObject } from "./types.ts";
16
+ /** How filter values are spelled. Determines what oat can express, not merely what it sends. */
17
+ export type FilterGrammar =
18
+ /** `field.op.value`, with `and(...)`/`or(...)` — PostgREST and friends. */
19
+ "postgrest"
20
+ /** `field=op:value`, e.g. `?price=gt:10`. */
21
+ | "colon"
22
+ /** One parameter per field, equality only — `?status=active`. */
23
+ | "equality";
24
+ /**
25
+ * How a sort direction is spelled.
26
+ *
27
+ * The role of the parameter and the *grammar* of its value are separate problems, and generalising
28
+ * only the first is a half-measure: an API that names the parameter `sort` but expects `-name`
29
+ * receives `name.asc`, ignores or rejects it, and reads as a backend with broken sorting rather
30
+ * than one speaking a dialect oat cannot write.
31
+ */
32
+ export type SortGrammar =
33
+ /** `field.asc` / `field.desc`, comma-separated — PostgREST. */
34
+ "dotted"
35
+ /** `-field` for descending, bare for ascending — JSON:API. */
36
+ | "prefixed"
37
+ /** `field:asc` / `field:desc`. */
38
+ | "colon"
39
+ /** `field asc` / `field desc`, as SQL writes it. */
40
+ | "spaced";
41
+ /** How a sparse fieldset is spelled. */
42
+ export type SelectGrammar =
43
+ /** `id,name` — comma-separated field names. */
44
+ "csv"
45
+ /** `fields[type]=id,name` — JSON:API's per-type bracket form. */
46
+ | "bracketed";
47
+ export interface QueryConventions {
48
+ /** Parameter carrying a filter expression, when the endpoint has one. */
49
+ filter?: string;
50
+ /** Parameter selecting sort order. */
51
+ order?: string;
52
+ /** Parameter selecting a sparse fieldset. */
53
+ select?: string;
54
+ /** Free-text search parameter. */
55
+ search?: string;
56
+ /** Page-size parameter. */
57
+ limit?: string;
58
+ /** 1-based page number. */
59
+ page?: string;
60
+ /** Zero-based row offset, where the API pages by offset rather than page number. */
61
+ offset?: string;
62
+ /** Opaque forward cursor. */
63
+ cursor?: string;
64
+ grammar: FilterGrammar;
65
+ /** How sort directions are written for the `order` parameter. */
66
+ sortGrammar: SortGrammar;
67
+ /** How a sparse fieldset is written for the `select` parameter. */
68
+ selectGrammar: SelectGrammar;
69
+ /**
70
+ * Response header carrying RFC 8288 pagination links, when the document declares one.
71
+ *
72
+ * Its presence changes how "are there more pages" is answered: under Link pagination the
73
+ * *absence* of `rel="next"` means no, so a response with no header at all is a definite
74
+ * "false" rather than an unknown. Without knowing the API publishes such a header, the two
75
+ * are indistinguishable and the check that asks has to stand down.
76
+ */
77
+ linkHeader?: string;
78
+ /** Where each is spelled in the response envelope, when the schema declares one. */
79
+ envelope: {
80
+ total?: string;
81
+ hasMore?: string;
82
+ nextCursor?: string;
83
+ page?: string;
84
+ limit?: string;
85
+ };
86
+ }
87
+ export declare function deriveQueryConventions(parameters: readonly ParameterObject[], collection: CollectionShape | null, declared?: {
88
+ grammar?: string;
89
+ }, onGuess?: (message: string) => void,
90
+ /** Response headers the success response declares, by name. */
91
+ responseHeaders?: readonly string[]): QueryConventions;
92
+ /** Renders one filter term in whichever grammar the endpoint speaks. */
93
+ export declare function filterTerm(conventions: QueryConventions, field: string, op: "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "like", value: string | number): Record<string, string> | null;
94
+ /** Renders a sort term in whichever grammar the endpoint speaks. */
95
+ export declare function sortTerm(conventions: QueryConventions, field: string, direction: "asc" | "desc"): string;
96
+ /**
97
+ * Renders a sparse fieldset, returning the parameter name alongside the value.
98
+ *
99
+ * JSON:API puts the resource type in the parameter name rather than the value, so the two cannot
100
+ * be chosen independently — which is why this returns a pair rather than a string.
101
+ */
102
+ export declare function selectTerm(conventions: QueryConventions, fields: readonly string[], resource: string): Record<string, string> | null;
103
+ //# sourceMappingURL=conventions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conventions.d.ts","sourceRoot":"","sources":["../../src/spec/conventions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AACtD,OAAO,KAAK,EAAE,eAAe,EAAgB,MAAM,YAAY,CAAA;AAE/D,gGAAgG;AAChG,MAAM,MAAM,aAAa;AACxB,2EAA2E;AACzE,WAAW;AACb,6CAA6C;GAC3C,OAAO;AACT,iEAAiE;GAC/D,UAAU,CAAA;AAEb;;;;;;;GAOG;AACH,MAAM,MAAM,WAAW;AACtB,+DAA+D;AAC7D,QAAQ;AACV,8DAA8D;GAC5D,UAAU;AACZ,kCAAkC;GAChC,OAAO;AACT,oDAAoD;GAClD,QAAQ,CAAA;AAEX,wCAAwC;AACxC,MAAM,MAAM,aAAa;AACxB,+CAA+C;AAC7C,KAAK;AACP,iEAAiE;GAC/D,WAAW,CAAA;AAEd,MAAM,WAAW,gBAAgB;IAChC,yEAAyE;IACzE,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,sCAAsC;IACtC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,6CAA6C;IAC7C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,kCAAkC;IAClC,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,2BAA2B;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,oFAAoF;IACpF,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,6BAA6B;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,OAAO,EAAE,aAAa,CAAA;IACtB,iEAAiE;IACjE,WAAW,EAAE,WAAW,CAAA;IACxB,mEAAmE;IACnE,aAAa,EAAE,aAAa,CAAA;IAC5B;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,oFAAoF;IACpF,QAAQ,EAAE;QACT,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,UAAU,CAAC,EAAE,MAAM,CAAA;QACnB,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,KAAK,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;CACD;AAkJD,wBAAgB,sBAAsB,CACrC,UAAU,EAAE,SAAS,eAAe,EAAE,EACtC,UAAU,EAAE,eAAe,GAAG,IAAI,EAClC,QAAQ,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,EAC/B,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI;AACnC,+DAA+D;AAC/D,eAAe,GAAE,SAAS,MAAM,EAAO,GACrC,gBAAgB,CAkElB;AAED,wEAAwE;AACxE,wBAAgB,UAAU,CACzB,WAAW,EAAE,gBAAgB,EAC7B,KAAK,EAAE,MAAM,EACb,EAAE,EAAE,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK,GAAG,MAAM,EACvD,KAAK,EAAE,MAAM,GAAG,MAAM,GACpB,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAc/B;AAGD,oEAAoE;AACpE,wBAAgB,QAAQ,CACvB,WAAW,EAAE,gBAAgB,EAC7B,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,KAAK,GAAG,MAAM,GACvB,MAAM,CAaR;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CACzB,WAAW,EAAE,gBAAgB,EAC7B,MAAM,EAAE,SAAS,MAAM,EAAE,EACzB,QAAQ,EAAE,MAAM,GACd,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAS/B"}
@@ -0,0 +1,275 @@
1
+ /**
2
+ * What a list endpoint's query parameters and response envelope *mean*, derived from the
3
+ * document rather than assumed.
4
+ *
5
+ * Checks used to match on literal names — `"filter"`, `"page"`, `envelope.hasMore` — which made
6
+ * roughly half of them fire only against APIs that happened to share the fixture's spelling.
7
+ * Everywhere else they were skipped, and skipped silently, so a quiet run on an unfamiliar API
8
+ * read exactly like a clean one.
9
+ *
10
+ * Names are matched against the aliases real APIs actually use, and the shape of the declared
11
+ * schema breaks ties. Anything still ambiguous is left `undefined` — the check then reports that
12
+ * it did not apply, and `x-query` can state the answer outright.
13
+ */
14
+ /*
15
+ * Aliases seen across real APIs. Order matters only for readability; a parameter is matched
16
+ * case-insensitively and with separators normalised, so `perPage`, `per_page` and `PerPage` are
17
+ * one entry.
18
+ */
19
+ const ALIASES = {
20
+ cursor: ["cursor", "after", "starting_after", "next", "page_token", "continuation"],
21
+ filter: ["filter", "where", "query", "conditions"],
22
+ limit: ["limit", "per_page", "page_size", "pagesize", "count", "max_results", "top", "size"],
23
+ offset: ["offset", "skip", "start", "from"],
24
+ order: ["order", "order_by", "sort", "sort_by", "ordering"],
25
+ page: ["page", "page_number", "pagenum", "p"],
26
+ search: ["q", "search", "query_text", "term", "keyword", "text"],
27
+ select: ["select", "fields", "field", "include_fields", "projection", "only"],
28
+ };
29
+ function normalise(name) {
30
+ return name
31
+ /* A bracketed suffix is a *value* carried in the parameter name — `fields[articles]`,
32
+ * `filter[status]` — not part of the role's name. Matching the whole string against the
33
+ * alias list would fail to recognise the role at all, and the check that needed it would
34
+ * report itself inapplicable against an API that plainly supports it. */
35
+ .replace(/\[[^\]]*\]$/, "")
36
+ .replace(/([a-z0-9])([A-Z])/g, "$1_$2")
37
+ .toLowerCase()
38
+ .replace(/[-\s]/g, "_");
39
+ }
40
+ function schemaOf(parameter) {
41
+ return parameter.schema ?? {};
42
+ }
43
+ function isInteger(parameter) {
44
+ const type = schemaOf(parameter).type;
45
+ return type === "integer" || type === "number";
46
+ }
47
+ /**
48
+ * `limit` and `page` are both bare integers, and several APIs spell one of them with a word that
49
+ * belongs to the other's alias list — `count` most notoriously. The declared schema settles it:
50
+ * a page size is the one with an upper bound, a page number the one that starts at 1.
51
+ */
52
+ function resolvePagingRoles(candidates) {
53
+ const roles = {};
54
+ for (const parameter of candidates) {
55
+ if (!isInteger(parameter))
56
+ continue;
57
+ const name = normalise(parameter.name);
58
+ const schema = schemaOf(parameter);
59
+ const hasMaximum = typeof schema.maximum === "number";
60
+ const startsAtOne = schema.minimum === 1;
61
+ const hasDefault = schema.default !== undefined;
62
+ if (roles.limit === undefined && ALIASES.limit.includes(name)) {
63
+ /* `count` is a page size only when it reads like one; otherwise it is a total. */
64
+ if (name !== "count" || hasMaximum || hasDefault) {
65
+ roles.limit = parameter.name;
66
+ continue;
67
+ }
68
+ }
69
+ if (roles.page === undefined && ALIASES.page.includes(name) && !hasMaximum) {
70
+ roles.page = parameter.name;
71
+ continue;
72
+ }
73
+ if (roles.offset === undefined && ALIASES.offset.includes(name)) {
74
+ roles.offset = parameter.name;
75
+ continue;
76
+ }
77
+ /* Unnamed but unmistakable: a bounded integer with a default is a page size. */
78
+ if (roles.limit === undefined && hasMaximum && hasDefault)
79
+ roles.limit = parameter.name;
80
+ else if (roles.page === undefined && startsAtOne && !hasMaximum)
81
+ roles.page = parameter.name;
82
+ }
83
+ return roles;
84
+ }
85
+ /**
86
+ * Infers the filter grammar from the parameter's declared examples, which is where APIs
87
+ * describing a grammar almost always demonstrate it. Defaults to one-parameter-per-field
88
+ * equality — the assumption that holds for the widest range of APIs, and the one whose checks
89
+ * degrade most gracefully when wrong.
90
+ */
91
+ function inferGrammar(filter) {
92
+ if (filter === undefined)
93
+ return "equality";
94
+ const schema = schemaOf(filter);
95
+ const samples = [
96
+ ...(Array.isArray(schema.examples) ? schema.examples : []),
97
+ ...(schema.example === undefined ? [] : [schema.example]),
98
+ ...(typeof filter.description === "string" ? [filter.description] : []),
99
+ ].filter((value) => typeof value === "string");
100
+ const text = samples.join(" ");
101
+ /* Named outright in the description is the strongest signal — documents that describe a
102
+ * grammar almost always name it before demonstrating it. */
103
+ if (/postgrest/i.test(text))
104
+ return "postgrest";
105
+ if (/\b[\w.]+\.(eq|neq|gt|gte|lt|lte|like|ilike|in|nin|is|contains)\b/.test(text))
106
+ return "postgrest";
107
+ /* `field.op.value` written as a schema rather than an instance. */
108
+ if (/\bfield\.op\.value\b/i.test(text))
109
+ return "postgrest";
110
+ if (/\b[\w.]+=(eq|neq|gt|gte|lt|lte|like|in):/.test(text))
111
+ return "colon";
112
+ return "equality";
113
+ }
114
+ /**
115
+ * Infers the sort grammar from what the document demonstrates.
116
+ *
117
+ * Descriptions and examples are where an API shows its hand — `sort=-created_at` is unambiguous,
118
+ * and so is `field:desc`. Defaults to the dotted form, which is the most common and, being an
119
+ * unrecognised token rather than a wrong direction, fails loudly rather than silently sorting
120
+ * backwards when wrong.
121
+ */
122
+ function inferSortGrammar(order) {
123
+ if (order === undefined)
124
+ return "dotted";
125
+ const schema = order.schema ?? {};
126
+ const text = [
127
+ ...(Array.isArray(schema.examples) ? schema.examples : []),
128
+ ...(schema.example === undefined ? [] : [schema.example]),
129
+ ...(typeof order.description === "string" ? [order.description] : []),
130
+ ]
131
+ .filter((value) => typeof value === "string")
132
+ .join(" ");
133
+ if (/\b[\w.]+\.(asc|desc)\b/i.test(text))
134
+ return "dotted";
135
+ if (/\b[\w.]+:(asc|desc)\b/i.test(text))
136
+ return "colon";
137
+ if (/\b[\w.]+\s+(asc|desc)\b/i.test(text))
138
+ return "spaced";
139
+ /* A leading minus is the JSON:API convention; the "-" must precede a field name, not merely
140
+ * appear in prose, so the match is anchored to a token boundary. */
141
+ if (/(^|[\s,="'])-[a-z_][\w.]*/i.test(text) && /descend/i.test(text))
142
+ return "prefixed";
143
+ if (/(^|[\s,="'])-[a-z_][\w.]*/i.test(text))
144
+ return "prefixed";
145
+ return "dotted";
146
+ }
147
+ /** Infers how a sparse fieldset is written. `fields[type]=` is JSON:API's and unmistakable. */
148
+ function inferSelectGrammar(select) {
149
+ if (select === undefined)
150
+ return "csv";
151
+ if (/\[[^\]]+\]$/.test(select.name))
152
+ return "bracketed";
153
+ const description = typeof select.description === "string" ? select.description : "";
154
+ return /fields\[[^\]]+\]/.test(description) ? "bracketed" : "csv";
155
+ }
156
+ export function deriveQueryConventions(parameters, collection, declared, onGuess,
157
+ /** Response headers the success response declares, by name. */
158
+ responseHeaders = []) {
159
+ const query = parameters.filter((p) => p.in === "query");
160
+ const byRole = (role) => query.find((p) => ALIASES[role].includes(normalise(p.name)))?.name;
161
+ const paging = resolvePagingRoles(query);
162
+ const filterName = byRole("filter");
163
+ const filterParam = query.find((p) => p.name === filterName);
164
+ const grammar = declared?.grammar === "postgrest" || declared?.grammar === "colon" || declared?.grammar === "equality"
165
+ ? declared.grammar
166
+ : inferGrammar(filterParam);
167
+ if (declared?.grammar === undefined &&
168
+ filterParam !== undefined &&
169
+ grammar === "equality" &&
170
+ schemaOf(filterParam).type === "string") {
171
+ /* A single free-text `filter` parameter almost never means equality-per-field; it means a
172
+ * grammar oat could not recognise. Saying so beats sending `?name=value` and reading the
173
+ * shrug that comes back as a defect. */
174
+ onGuess?.(`parameter "${filterParam.name}" takes a filter expression whose grammar could not be ` +
175
+ "recognised from its description or examples, so oat assumed one parameter per field. " +
176
+ 'Declare it with x-query: { grammar: "postgrest" | "colon" | "equality" }');
177
+ }
178
+ const orderParam = query.find((p) => p.name === byRole("order"));
179
+ const selectParam = query.find((p) => p.name === byRole("select"));
180
+ const conventions = {
181
+ envelope: {},
182
+ grammar,
183
+ selectGrammar: inferSelectGrammar(selectParam),
184
+ sortGrammar: inferSortGrammar(orderParam),
185
+ };
186
+ const link = responseHeaders.find((name) => name.toLowerCase() === "link");
187
+ if (link !== undefined)
188
+ conventions.linkHeader = link;
189
+ if (filterName !== undefined)
190
+ conventions.filter = filterName;
191
+ const order = byRole("order");
192
+ if (order !== undefined)
193
+ conventions.order = order;
194
+ const select = byRole("select");
195
+ if (select !== undefined)
196
+ conventions.select = select;
197
+ const search = byRole("search");
198
+ if (search !== undefined)
199
+ conventions.search = search;
200
+ const cursor = byRole("cursor");
201
+ if (cursor !== undefined)
202
+ conventions.cursor = cursor;
203
+ if (paging.limit !== undefined)
204
+ conventions.limit = paging.limit;
205
+ if (paging.page !== undefined)
206
+ conventions.page = paging.page;
207
+ if (paging.offset !== undefined)
208
+ conventions.offset = paging.offset;
209
+ /* The envelope is already derived from the 200 schema when the shape was resolved — reuse it
210
+ * rather than matching on literal key names a second time. */
211
+ const pagination = collection?.pagination;
212
+ if (pagination?.countKey !== undefined)
213
+ conventions.envelope.total = pagination.countKey;
214
+ if (pagination?.hasMoreKey !== undefined)
215
+ conventions.envelope.hasMore = pagination.hasMoreKey;
216
+ if (pagination?.cursorKey !== undefined)
217
+ conventions.envelope.nextCursor = pagination.cursorKey;
218
+ if (pagination?.pageKey !== undefined)
219
+ conventions.envelope.page = pagination.pageKey;
220
+ if (pagination?.limitKey !== undefined)
221
+ conventions.envelope.limit = pagination.limitKey;
222
+ return conventions;
223
+ }
224
+ /** Renders one filter term in whichever grammar the endpoint speaks. */
225
+ export function filterTerm(conventions, field, op, value) {
226
+ const parameter = conventions.filter;
227
+ switch (conventions.grammar) {
228
+ case "postgrest":
229
+ if (parameter === undefined)
230
+ return null;
231
+ return { [parameter]: `${field}.${op}.${value}` };
232
+ case "colon":
233
+ if (parameter === undefined)
234
+ return null;
235
+ return { [parameter]: `${field}=${op}:${value}` };
236
+ case "equality":
237
+ /* Only equality is expressible, so anything else has no representation and the check
238
+ * that wanted it must stand down rather than send something meaningless. */
239
+ return op === "eq" ? { [field]: String(value) } : null;
240
+ }
241
+ }
242
+ /** Renders a sort term in whichever grammar the endpoint speaks. */
243
+ export function sortTerm(conventions, field, direction) {
244
+ switch (conventions.sortGrammar) {
245
+ case "dotted":
246
+ return `${field}.${direction}`;
247
+ case "colon":
248
+ return `${field}:${direction}`;
249
+ case "spaced":
250
+ return `${field} ${direction}`;
251
+ case "prefixed":
252
+ /* Ascending is the bare field name: there is no "+" form in this grammar, and sending
253
+ * one would be a token the API has never seen. */
254
+ return direction === "desc" ? `-${field}` : field;
255
+ }
256
+ }
257
+ /**
258
+ * Renders a sparse fieldset, returning the parameter name alongside the value.
259
+ *
260
+ * JSON:API puts the resource type in the parameter name rather than the value, so the two cannot
261
+ * be chosen independently — which is why this returns a pair rather than a string.
262
+ */
263
+ export function selectTerm(conventions, fields, resource) {
264
+ const parameter = conventions.select;
265
+ if (parameter === undefined)
266
+ return null;
267
+ const value = fields.join(",");
268
+ if (conventions.selectGrammar === "csv")
269
+ return { [parameter]: value };
270
+ /* Already bracketed in the document — `fields[articles]` — so the name is used verbatim.
271
+ * Otherwise the resource name is inserted, which is what the grammar requires. */
272
+ const name = /\[[^\]]+\]$/.test(parameter) ? parameter : `${parameter}[${resource}]`;
273
+ return { [name]: value };
274
+ }
275
+ //# sourceMappingURL=conventions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conventions.js","sourceRoot":"","sources":["../../src/spec/conventions.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Reads the `x-*` meta tags documented in the README.
3
+ *
4
+ * Every reader follows the same contract: explicit tag → heuristic → `null` plus a recorded
5
+ * gap. oat never silently guesses; an unresolved tag becomes a COVERAGE_GAP in the report
6
+ * naming the tag that would close it.
7
+ */
8
+ import type { Endpoint, OperationObject } from "./types.ts";
9
+ export type EntityAction = "create" | "list" | "read" | "update" | "delete" | "action";
10
+ export interface EntityRef {
11
+ name: string;
12
+ action: EntityAction;
13
+ identity?: string;
14
+ source: "tag" | "heuristic";
15
+ }
16
+ export interface QueryCapability {
17
+ filterable: string[];
18
+ sortable: string[];
19
+ searchable: string[];
20
+ selectable: string[];
21
+ maxLimit?: number;
22
+ defaultOrder?: string;
23
+ stableTiebreak?: string;
24
+ source: "tag" | "heuristic";
25
+ }
26
+ export interface AsyncSpec {
27
+ poll: string;
28
+ idFrom?: string;
29
+ until?: string;
30
+ successWhen?: string;
31
+ timeoutMs: number;
32
+ pollIntervalMs: number;
33
+ }
34
+ export interface EffectSpec {
35
+ entity: string;
36
+ op: "create" | "append" | "update" | "delete" | "replace";
37
+ count?: number;
38
+ }
39
+ export interface Gap {
40
+ operationId: string;
41
+ tag: string;
42
+ detail: string;
43
+ }
44
+ export declare class GapCollector {
45
+ readonly gaps: Gap[];
46
+ record(operationId: string, tag: string, detail: string): void;
47
+ }
48
+ export declare function readInvalidate(op: OperationObject): string[];
49
+ export declare function singularise(word: string): string;
50
+ export declare function readEntity(endpoint: Endpoint, gaps?: GapCollector): EntityRef | null;
51
+ export declare function readQueryCapability(endpoint: Endpoint, itemSchema: Record<string, unknown> | null, gaps?: GapCollector): QueryCapability | null;
52
+ export declare function readAsync(op: OperationObject): AsyncSpec | null;
53
+ export declare function readEffects(op: OperationObject): EffectSpec[];
54
+ export interface InviteSpec {
55
+ invite: string;
56
+ accept: string;
57
+ revoke: string;
58
+ granteeField: string;
59
+ tokenPointer: string;
60
+ grantPointer: string;
61
+ }
62
+ export declare function readInvite(op: OperationObject): InviteSpec | null;
63
+ export declare function readSoftDelete(op: OperationObject): string | null;
64
+ export declare function readImmutable(op: OperationObject): string[];
65
+ export declare function readGenerated(op: OperationObject, bodySchema: unknown): string[];
66
+ export declare function readCost(op: OperationObject): "low" | "medium" | "high";
67
+ export declare function readFlag(op: OperationObject, key: string): boolean;
68
+ export declare function readCleanup(op: OperationObject): string | null;
69
+ export declare function readTenantSource(endpoint: Endpoint): "tag" | "heuristic" | null;
70
+ export declare function readTenantParam(endpoint: Endpoint, gaps?: GapCollector): string | null;
71
+ export declare function pathParameterNames(path: string): string[];
72
+ /** Path parameters declared as roots — resources oat cannot create and must be given. */
73
+ export declare function readRootParams(op: OperationObject): string[];
74
+ //# sourceMappingURL=extensions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extensions.d.ts","sourceRoot":"","sources":["../../src/spec/extensions.ts"],"names":[],"mappings":"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"}