@loutrejs/loutre 0.5.0 → 0.7.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 (188) hide show
  1. package/README.md +63 -32
  2. package/dist/adapters/aws-lambda.d.ts +5 -3
  3. package/dist/adapters/aws-lambda.js +53 -23
  4. package/dist/adapters/bun.d.ts +7 -4
  5. package/dist/adapters/bun.js +52 -42
  6. package/dist/adapters/cloudflare-workers.d.ts +5 -3
  7. package/dist/adapters/cloudflare-workers.js +23 -28
  8. package/dist/adapters/deno.d.ts +7 -4
  9. package/dist/adapters/deno.js +94 -57
  10. package/dist/adapters/electron.d.ts +13 -5
  11. package/dist/adapters/electron.js +76 -28
  12. package/dist/application/definition.d.ts +31 -0
  13. package/dist/application/definition.js +16 -0
  14. package/dist/application/index.d.ts +4 -63
  15. package/dist/application/index.js +2 -11
  16. package/dist/application/kernel-internal.d.ts +2 -0
  17. package/dist/application/kernel-internal.js +10 -0
  18. package/dist/application/kernel.d.ts +22 -0
  19. package/dist/application/kernel.js +65 -0
  20. package/dist/core/diagnostic.d.ts +9 -0
  21. package/dist/core/diagnostic.js +9 -0
  22. package/dist/core/extension.d.ts +118 -0
  23. package/dist/core/extension.js +73 -0
  24. package/dist/core/generic-layer.d.ts +26 -0
  25. package/dist/core/generic-layer.js +116 -0
  26. package/dist/core/index.d.ts +32 -15
  27. package/dist/core/index.js +15 -15
  28. package/dist/core/injection.d.ts +4 -13
  29. package/dist/core/injection.js +63 -0
  30. package/dist/core/instrumentation.d.ts +36 -0
  31. package/dist/core/model/context.d.ts +18 -0
  32. package/dist/core/model/context.js +34 -0
  33. package/dist/core/model/lifecycle.d.ts +5 -0
  34. package/dist/core/model/lifecycle.js +44 -0
  35. package/dist/core/model/modules.d.ts +2 -0
  36. package/dist/core/model/modules.js +101 -0
  37. package/dist/core/model/providers.d.ts +4 -0
  38. package/dist/core/model/providers.js +74 -0
  39. package/dist/core/model/snapshot.d.ts +6 -0
  40. package/dist/core/model/snapshot.js +31 -0
  41. package/dist/core/model/types.d.ts +78 -0
  42. package/dist/core/model/types.js +1 -0
  43. package/dist/core/model/validation.d.ts +4 -0
  44. package/dist/core/model/validation.js +40 -0
  45. package/dist/core/model/visibility.d.ts +4 -0
  46. package/dist/core/model/visibility.js +20 -0
  47. package/dist/core/model.d.ts +10 -0
  48. package/dist/core/model.js +346 -0
  49. package/dist/core/module.d.ts +8 -13
  50. package/dist/core/source-location.d.ts +8 -0
  51. package/dist/core/source-location.js +31 -0
  52. package/dist/core/token-internal.d.ts +3 -0
  53. package/dist/core/token-internal.js +4 -0
  54. package/dist/core/type.d.ts +7 -0
  55. package/dist/core/type.js +4 -0
  56. package/dist/devtools/capsule.d.ts +51 -0
  57. package/dist/devtools/capsule.js +245 -0
  58. package/dist/devtools/index.d.ts +11 -0
  59. package/dist/devtools/index.js +6 -0
  60. package/dist/devtools/inspector.d.ts +57 -0
  61. package/dist/devtools/inspector.js +1 -0
  62. package/dist/devtools/module.d.ts +18 -0
  63. package/dist/devtools/module.js +26 -0
  64. package/dist/devtools/observer.d.ts +77 -0
  65. package/dist/devtools/observer.js +10 -0
  66. package/dist/devtools/protocol.d.ts +1 -0
  67. package/dist/devtools/protocol.js +1 -0
  68. package/dist/devtools/provider-instrumentation.d.ts +14 -0
  69. package/dist/devtools/provider-instrumentation.js +117 -0
  70. package/dist/devtools/runtime.d.ts +20 -0
  71. package/dist/devtools/runtime.js +519 -0
  72. package/dist/devtools/values.d.ts +35 -0
  73. package/dist/devtools/values.js +175 -0
  74. package/dist/generated/version.d.ts +1 -1
  75. package/dist/generated/version.js +1 -1
  76. package/dist/graph/index.d.ts +2 -3
  77. package/dist/graph/index.js +1 -3
  78. package/dist/graph/model.d.ts +34 -0
  79. package/dist/graph/model.js +127 -0
  80. package/dist/http/auth.d.ts +46 -0
  81. package/dist/http/auth.js +96 -0
  82. package/dist/http/client.d.ts +28 -31
  83. package/dist/http/client.js +58 -44
  84. package/dist/http/cors-internal.d.ts +29 -0
  85. package/dist/http/cors-internal.js +160 -0
  86. package/dist/http/cors.d.ts +5 -8
  87. package/dist/http/cors.js +7 -165
  88. package/dist/http/extension.d.ts +348 -0
  89. package/dist/http/extension.js +1061 -0
  90. package/dist/http/index.d.ts +9 -7
  91. package/dist/http/index.js +5 -7
  92. package/dist/{application → http}/openapi.d.ts +12 -3
  93. package/dist/{application → http}/openapi.js +164 -102
  94. package/dist/http/path.d.ts +1 -42
  95. package/dist/http/path.js +1 -2
  96. package/dist/http/runtime/request.d.ts +17 -0
  97. package/dist/http/runtime/request.js +109 -0
  98. package/dist/http/runtime/response.d.ts +18 -0
  99. package/dist/http/runtime/response.js +108 -0
  100. package/dist/index.d.ts +4 -0
  101. package/dist/index.js +2 -0
  102. package/dist/message-port/extension.d.ts +66 -0
  103. package/dist/message-port/extension.js +411 -0
  104. package/dist/message-port/index.d.ts +4 -73
  105. package/dist/message-port/index.js +2 -189
  106. package/dist/message-port/transport.d.ts +11 -0
  107. package/dist/message-port/transport.js +45 -0
  108. package/dist/runtime/async-iterator-lifecycle.d.ts +12 -0
  109. package/dist/runtime/async-iterator-lifecycle.js +95 -0
  110. package/dist/runtime/capability.d.ts +11 -11
  111. package/dist/runtime/capability.js +3 -3
  112. package/dist/runtime/di.d.ts +5 -21
  113. package/dist/runtime/di.js +22 -289
  114. package/dist/runtime/execution-tracker.d.ts +8 -0
  115. package/dist/runtime/execution-tracker.js +54 -0
  116. package/dist/runtime/index.d.ts +15 -9
  117. package/dist/runtime/index.js +9 -9
  118. package/dist/runtime/ingress-gate.d.ts +7 -0
  119. package/dist/runtime/ingress-gate.js +33 -0
  120. package/dist/runtime/kernel.d.ts +25 -0
  121. package/dist/runtime/kernel.js +447 -0
  122. package/dist/runtime/logger.d.ts +2 -0
  123. package/dist/runtime/logger.js +2 -0
  124. package/dist/runtime/node-capabilities.d.ts +1 -1
  125. package/dist/runtime/node-capabilities.js +1 -1
  126. package/dist/runtime/server-port.d.ts +2 -0
  127. package/dist/runtime/server-port.js +11 -0
  128. package/dist/tasks/extension.d.ts +144 -0
  129. package/dist/tasks/extension.js +525 -0
  130. package/dist/tasks/index.d.ts +2 -0
  131. package/dist/tasks/index.js +1 -0
  132. package/dist/tasks/runtime/cron.d.ts +6 -0
  133. package/dist/tasks/runtime/cron.js +89 -0
  134. package/dist/websocket/errors.d.ts +9 -0
  135. package/dist/websocket/errors.js +18 -0
  136. package/dist/websocket/extension.d.ts +154 -0
  137. package/dist/websocket/extension.js +533 -0
  138. package/dist/websocket/index.d.ts +3 -0
  139. package/dist/websocket/index.js +2 -0
  140. package/package.json +16 -16
  141. package/dist/application/binding.d.ts +0 -33
  142. package/dist/application/binding.js +0 -295
  143. package/dist/application/host.d.ts +0 -8
  144. package/dist/application/host.js +0 -14
  145. package/dist/application/queue.d.ts +0 -3
  146. package/dist/application/queue.js +0 -4
  147. package/dist/application/scheduler.d.ts +0 -2
  148. package/dist/application/scheduler.js +0 -45
  149. package/dist/core/context-key.d.ts +0 -16
  150. package/dist/core/context-key.js +0 -17
  151. package/dist/core/contract-internal.d.ts +0 -22
  152. package/dist/core/contract-internal.js +0 -61
  153. package/dist/core/contract.d.ts +0 -96
  154. package/dist/core/contract.js +0 -88
  155. package/dist/core/execution.d.ts +0 -78
  156. package/dist/core/execution.js +0 -61
  157. package/dist/core/implementation.d.ts +0 -44
  158. package/dist/core/implementation.js +0 -35
  159. package/dist/core/layer.d.ts +0 -137
  160. package/dist/core/layer.js +0 -81
  161. package/dist/graph/compiler.d.ts +0 -4
  162. package/dist/graph/compiler.js +0 -228
  163. package/dist/graph/graph.d.ts +0 -18
  164. package/dist/graph/graph.js +0 -1118
  165. package/dist/graph/ir.d.ts +0 -175
  166. package/dist/http/application.d.ts +0 -18
  167. package/dist/http/application.js +0 -515
  168. package/dist/http/basic-auth.d.ts +0 -37
  169. package/dist/http/basic-auth.js +0 -67
  170. package/dist/http/definitions.d.ts +0 -363
  171. package/dist/http/definitions.js +0 -310
  172. package/dist/http/hosted-protocol.d.ts +0 -4
  173. package/dist/http/hosted-protocol.js +0 -4
  174. package/dist/http/internal.d.ts +0 -8
  175. package/dist/http/internal.js +0 -1
  176. package/dist/http/params.d.ts +0 -2
  177. package/dist/http/params.js +0 -19
  178. package/dist/http/request-body.d.ts +0 -4
  179. package/dist/http/request-body.js +0 -6
  180. package/dist/http/validate.d.ts +0 -20
  181. package/dist/http/validate.js +0 -6
  182. package/dist/message-port/environment.d.ts +0 -2
  183. package/dist/message-port/environment.js +0 -4
  184. package/dist/runtime/application.d.ts +0 -38
  185. package/dist/runtime/application.js +0 -339
  186. package/dist/runtime/pipeline.d.ts +0 -11
  187. package/dist/runtime/pipeline.js +0 -161
  188. /package/dist/{graph/ir.js → core/instrumentation.js} +0 -0
@@ -0,0 +1,175 @@
1
+ const defaultMaxValueBytes = 64 * 1024;
2
+ const defaultMaxDepth = 8;
3
+ export function previewDevtoolsValue(value, options = {}) {
4
+ const maxValueBytes = options.maxValueBytes ?? defaultMaxValueBytes;
5
+ const maxDepth = options.maxDepth ?? defaultMaxDepth;
6
+ const redactions = new Set((options.redact ?? []).map((entry) => entry.trim().toLowerCase()));
7
+ let redacted = false;
8
+ let truncated = false;
9
+ const seen = new WeakSet();
10
+ const encode = (current, depth, path) => {
11
+ if (depth > maxDepth) {
12
+ truncated = true;
13
+ return '[Truncated]';
14
+ }
15
+ if (current === null)
16
+ return null;
17
+ switch (typeof current) {
18
+ case 'boolean':
19
+ case 'number':
20
+ case 'string':
21
+ return current;
22
+ case 'undefined':
23
+ return { $type: 'undefined' };
24
+ case 'bigint':
25
+ return { $type: 'bigint', value: current.toString() };
26
+ case 'function':
27
+ case 'symbol':
28
+ truncated = true;
29
+ return `[${typeof current}]`;
30
+ case 'object':
31
+ break;
32
+ }
33
+ if (current instanceof Date) {
34
+ return { $type: 'date', value: current.toISOString() };
35
+ }
36
+ if (current instanceof Uint8Array) {
37
+ return { $type: 'bytes', base64: bytesToBase64(current) };
38
+ }
39
+ if (seen.has(current)) {
40
+ truncated = true;
41
+ return '[Circular]';
42
+ }
43
+ seen.add(current);
44
+ try {
45
+ if (Array.isArray(current)) {
46
+ const output = [];
47
+ const descriptors = Object.getOwnPropertyDescriptors(current);
48
+ for (let index = 0; index < current.length; index += 1) {
49
+ const descriptor = descriptors[String(index)];
50
+ if (!descriptor) {
51
+ output.length = index + 1;
52
+ continue;
53
+ }
54
+ if (!('value' in descriptor)) {
55
+ truncated = true;
56
+ output[index] = '[Accessor]';
57
+ continue;
58
+ }
59
+ output[index] = encode(descriptor.value, depth + 1, [
60
+ ...path,
61
+ String(index),
62
+ ]);
63
+ }
64
+ return output;
65
+ }
66
+ if (current instanceof Map) {
67
+ return {
68
+ $type: 'map',
69
+ entries: [...current.entries()].map(([key, item], index) => [
70
+ encode(key, depth + 1, [...path, String(index), 'key']),
71
+ encode(item, depth + 1, [...path, String(index), 'value']),
72
+ ]),
73
+ };
74
+ }
75
+ if (current instanceof Set) {
76
+ return {
77
+ $type: 'set',
78
+ values: [...current.values()].map((item, index) => encode(item, depth + 1, [...path, String(index)])),
79
+ };
80
+ }
81
+ const output = {};
82
+ for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(current))) {
83
+ if (!descriptor.enumerable)
84
+ continue;
85
+ const nextPath = [...path, key];
86
+ if (shouldRedact(key, nextPath, redactions)) {
87
+ redacted = true;
88
+ output[key] = { $type: 'redacted' };
89
+ continue;
90
+ }
91
+ if (!('value' in descriptor)) {
92
+ truncated = true;
93
+ output[key] = '[Accessor]';
94
+ continue;
95
+ }
96
+ output[key] = encode(descriptor.value, depth + 1, nextPath);
97
+ }
98
+ return output;
99
+ }
100
+ finally {
101
+ seen.delete(current);
102
+ }
103
+ };
104
+ try {
105
+ const encoded = encode(value, 0, []);
106
+ const serialized = JSON.stringify(encoded);
107
+ const bytes = utf8ByteLength(serialized);
108
+ const preview = serialized.length > 512 ? `${serialized.slice(0, 509)}...` : serialized;
109
+ if (bytes > maxValueBytes) {
110
+ return {
111
+ preview,
112
+ truncated: true,
113
+ ...(redacted ? { redacted: true } : {}),
114
+ type: valueType(value),
115
+ };
116
+ }
117
+ return {
118
+ value: encoded,
119
+ preview,
120
+ ...(truncated ? { truncated: true } : {}),
121
+ ...(redacted ? { redacted: true } : {}),
122
+ type: valueType(value),
123
+ };
124
+ }
125
+ catch {
126
+ const type = safeValueType(value);
127
+ return {
128
+ preview: `[${type}]`,
129
+ truncated: true,
130
+ type,
131
+ };
132
+ }
133
+ }
134
+ function shouldRedact(key, path, redactions) {
135
+ if (redactions.size === 0)
136
+ return false;
137
+ const normalizedKey = key.toLowerCase();
138
+ const normalizedPath = path.join('.').toLowerCase();
139
+ return redactions.has(normalizedKey) || redactions.has(normalizedPath);
140
+ }
141
+ function valueType(value) {
142
+ if (value === null)
143
+ return 'null';
144
+ if (Array.isArray(value))
145
+ return 'array';
146
+ if (value instanceof Date)
147
+ return 'date';
148
+ if (value instanceof Uint8Array)
149
+ return 'bytes';
150
+ if (value instanceof Map)
151
+ return 'map';
152
+ if (value instanceof Set)
153
+ return 'set';
154
+ return typeof value === 'object' ? 'object' : typeof value;
155
+ }
156
+ function safeValueType(value) {
157
+ try {
158
+ return valueType(value);
159
+ }
160
+ catch {
161
+ return typeof value;
162
+ }
163
+ }
164
+ function utf8ByteLength(value) {
165
+ return new TextEncoder().encode(value).byteLength;
166
+ }
167
+ function bytesToBase64(value) {
168
+ let binary = '';
169
+ for (const byte of value)
170
+ binary += String.fromCharCode(byte);
171
+ const encoder = globalThis.btoa;
172
+ if (typeof encoder === 'function')
173
+ return encoder(binary);
174
+ return binary;
175
+ }
@@ -1 +1 @@
1
- export declare const LOUTRE_VERSION = "0.5.0";
1
+ export declare const LOUTRE_VERSION = "0.7.0";
@@ -1,2 +1,2 @@
1
1
  // package manifestとのずれを避けるため、scripts/generate-version.mjsがbuild前に更新する。
2
- export const LOUTRE_VERSION = '0.5.0';
2
+ export const LOUTRE_VERSION = '0.7.0';
@@ -1,3 +1,2 @@
1
- export * from './ir.js';
2
- export { StaticValidationError, assertValidCompilation, validateGraph, type ApplicationCompilationInput, } from './graph.js';
3
- export { buildApplicationGraph, compileApplication } from './compiler.js';
1
+ export { projectApplicationModel } from './model.js';
2
+ export type { ApplicationModelGraphIR, GraphEdgeIR, GraphNodeIR, JsonPrimitive, JsonValue, } from './model.js';
@@ -1,3 +1 @@
1
- export * from './ir.js';
2
- export { StaticValidationError, assertValidCompilation, validateGraph, } from './graph.js';
3
- export { buildApplicationGraph, compileApplication } from './compiler.js';
1
+ export { projectApplicationModel } from './model.js';
@@ -0,0 +1,34 @@
1
+ import { type ApplicationModel, type ApplicationModelEdge, type ApplicationModelNode, type Diagnostic, type SourceLocation } from '../core/index.js';
2
+ export type JsonPrimitive = string | number | boolean | null;
3
+ export type JsonValue = JsonPrimitive | {
4
+ readonly [key: string]: JsonValue;
5
+ } | readonly JsonValue[];
6
+ export interface GraphNodeIR {
7
+ readonly id: string;
8
+ readonly kind: ApplicationModelNode['kind'];
9
+ readonly name?: string;
10
+ readonly module?: string;
11
+ readonly executionKind?: string;
12
+ readonly capabilities?: readonly string[];
13
+ readonly extension?: {
14
+ readonly name: string;
15
+ readonly hostNamespace?: string;
16
+ readonly metadata?: JsonValue;
17
+ };
18
+ readonly attributes?: Readonly<Record<string, JsonValue>>;
19
+ readonly source?: SourceLocation;
20
+ }
21
+ export interface GraphEdgeIR {
22
+ readonly from: string;
23
+ readonly to: string;
24
+ readonly kind: ApplicationModelEdge['kind'];
25
+ }
26
+ export interface ApplicationModelGraphIR {
27
+ readonly nodes: readonly GraphNodeIR[];
28
+ readonly edges: readonly GraphEdgeIR[];
29
+ readonly diagnostics: readonly Diagnostic[];
30
+ readonly modules: readonly GraphNodeIR[];
31
+ readonly providers: readonly GraphNodeIR[];
32
+ readonly executions: readonly GraphNodeIR[];
33
+ }
34
+ export declare function projectApplicationModel(model: ApplicationModel): ApplicationModelGraphIR;
@@ -0,0 +1,127 @@
1
+ import { diagnostic, tokenName, } from '../core/index.js';
2
+ export function projectApplicationModel(model) {
3
+ const diagnostics = [...model.diagnostics];
4
+ const projectedExecutions = new Map();
5
+ for (const group of model.extensions) {
6
+ for (const execution of projectExtensionGroup(group, diagnostics)) {
7
+ projectedExecutions.set(execution.id, execution);
8
+ }
9
+ }
10
+ const nodes = model.nodes.map((node) => node.kind === 'execution'
11
+ ? (projectedExecutions.get(node.id) ?? projectExecutionBase(node))
12
+ : projectCoreNode(node));
13
+ const edges = model.edges.map((edge) => ({ ...edge }));
14
+ return Object.freeze({
15
+ nodes: Object.freeze(nodes),
16
+ edges: Object.freeze(edges),
17
+ diagnostics: Object.freeze(diagnostics),
18
+ modules: Object.freeze(nodes.filter((node) => node.kind === 'module')),
19
+ providers: Object.freeze(nodes.filter((node) => node.kind === 'provider')),
20
+ executions: Object.freeze(nodes.filter((node) => node.kind === 'execution')),
21
+ });
22
+ }
23
+ function projectCoreNode(node) {
24
+ switch (node.kind) {
25
+ case 'module':
26
+ return {
27
+ id: node.id,
28
+ kind: node.kind,
29
+ ...(node.source === undefined ? {} : { source: node.source }),
30
+ ...(node.name === undefined ? {} : { name: node.name }),
31
+ ...(node.description === undefined
32
+ ? {}
33
+ : { attributes: { description: node.description } }),
34
+ };
35
+ case 'provider':
36
+ return {
37
+ id: node.id,
38
+ kind: node.kind,
39
+ name: tokenName(node.token),
40
+ module: node.moduleId,
41
+ ...(node.source === undefined ? {} : { source: node.source }),
42
+ attributes: {
43
+ providerKind: node.provider.kind,
44
+ scope: node.provider.scope,
45
+ },
46
+ };
47
+ case 'lifecycle':
48
+ return {
49
+ id: node.id,
50
+ kind: node.kind,
51
+ name: node.phase,
52
+ module: node.moduleId,
53
+ ...(node.source === undefined ? {} : { source: node.source }),
54
+ };
55
+ case 'framework':
56
+ return {
57
+ id: node.id,
58
+ kind: node.kind,
59
+ name: node.name,
60
+ ...(node.source === undefined ? {} : { source: node.source }),
61
+ attributes: { frameworkKind: node.frameworkKind },
62
+ };
63
+ }
64
+ }
65
+ function projectExtensionGroup(group, diagnostics) {
66
+ return group.executions.map((execution) => {
67
+ let metadata;
68
+ const project = group.extension.projectGraph;
69
+ if (project) {
70
+ try {
71
+ const projected = project({ execution });
72
+ if (projected !== undefined) {
73
+ if (!isJsonValue(projected)) {
74
+ diagnostics.push(diagnostic('LUTRE_EXTENSION_PROJECTION_NOT_SERIALIZABLE', `Extension ${group.extension.name} returned non-serializable metadata.`, execution.id));
75
+ }
76
+ else {
77
+ metadata = projected;
78
+ }
79
+ }
80
+ }
81
+ catch (error) {
82
+ diagnostics.push(diagnostic('LUTRE_EXTENSION_PROJECTION', error instanceof Error ? error.message : String(error), execution.id));
83
+ }
84
+ }
85
+ return {
86
+ ...projectExecutionBase(execution),
87
+ extension: {
88
+ name: group.extension.name,
89
+ ...(group.extension.host === undefined
90
+ ? {}
91
+ : { hostNamespace: group.extension.host.namespace }),
92
+ ...(metadata === undefined ? {} : { metadata }),
93
+ },
94
+ };
95
+ });
96
+ }
97
+ function projectExecutionBase(execution) {
98
+ return {
99
+ id: execution.id,
100
+ kind: execution.kind,
101
+ module: execution.moduleId,
102
+ executionKind: execution.executionKind,
103
+ capabilities: execution.capabilities.map((capability) => capability.id),
104
+ ...(execution.source === undefined ? {} : { source: execution.source }),
105
+ };
106
+ }
107
+ function isJsonValue(value, ancestors = new Set()) {
108
+ if (value === null ||
109
+ typeof value === 'string' ||
110
+ typeof value === 'boolean') {
111
+ return true;
112
+ }
113
+ if (typeof value === 'number')
114
+ return Number.isFinite(value);
115
+ if (typeof value !== 'object')
116
+ return false;
117
+ if (ancestors.has(value))
118
+ return false;
119
+ const nextAncestors = new Set(ancestors).add(value);
120
+ if (Array.isArray(value)) {
121
+ return value.every((item) => isJsonValue(item, nextAncestors));
122
+ }
123
+ const prototype = Object.getPrototypeOf(value);
124
+ if (prototype !== Object.prototype && prototype !== null)
125
+ return false;
126
+ return Object.values(value).every((item) => isJsonValue(item, nextAncestors));
127
+ }
@@ -0,0 +1,46 @@
1
+ import type { HttpExecutionResult, HttpMiddleware, HttpMiddlewareContext } from './extension.js';
2
+ export interface BasicAuthCredentials {
3
+ readonly username: string;
4
+ readonly password: string;
5
+ }
6
+ export interface HttpAuthenticationFailure<TResponse extends string, TBody> {
7
+ readonly response: TResponse;
8
+ readonly body: TBody;
9
+ }
10
+ export type BasicAuthUnauthorized<TResponse extends string, TBody> = HttpAuthenticationFailure<TResponse, TBody>;
11
+ export type BearerAuthUnauthorized<TResponse extends string, TBody> = HttpAuthenticationFailure<TResponse, TBody>;
12
+ export interface BasicAuthContext {
13
+ readonly input: {
14
+ readonly headers: Readonly<Record<string, string | undefined>>;
15
+ };
16
+ }
17
+ export interface BearerAuthContext {
18
+ readonly input: {
19
+ readonly headers: Readonly<Record<string, string | undefined>>;
20
+ };
21
+ }
22
+ type AuthenticationChallengeHeaders = {
23
+ readonly 'www-authenticate': string;
24
+ };
25
+ type AuthenticationShortCircuit<TResponse extends string, TBody> = HttpExecutionResult<TResponse, TBody, AuthenticationChallengeHeaders>;
26
+ export interface BasicAuthDefinition<TContribution extends object, TResponse extends string, TUnauthorizedBody> {
27
+ readonly name?: string;
28
+ readonly realm: string;
29
+ readonly factory: () => BasicAuthRuntime<TContribution, TResponse, TUnauthorizedBody>;
30
+ }
31
+ export interface BasicAuthRuntime<TContribution extends object, TResponse extends string, TUnauthorizedBody> {
32
+ authenticate(credentials: BasicAuthCredentials): TContribution | null | undefined | Promise<TContribution | null | undefined>;
33
+ unauthorized(): HttpAuthenticationFailure<TResponse, TUnauthorizedBody>;
34
+ }
35
+ export declare function basicAuth<const TContribution extends object, const TResponse extends string, TUnauthorizedBody>(definition: BasicAuthDefinition<TContribution, TResponse, TUnauthorizedBody>): HttpMiddleware<TContribution, HttpMiddlewareContext, AuthenticationShortCircuit<TResponse, TUnauthorizedBody>>;
36
+ export interface BearerAuthDefinition<TContribution extends object, TResponse extends string, TUnauthorizedBody> {
37
+ readonly name?: string;
38
+ readonly realm: string;
39
+ readonly factory: () => BearerAuthRuntime<TContribution, TResponse, TUnauthorizedBody>;
40
+ }
41
+ export interface BearerAuthRuntime<TContribution extends object, TResponse extends string, TUnauthorizedBody> {
42
+ authenticate(token: string): TContribution | null | undefined | Promise<TContribution | null | undefined>;
43
+ unauthorized(): HttpAuthenticationFailure<TResponse, TUnauthorizedBody>;
44
+ }
45
+ export declare function bearerAuth<const TContribution extends object, const TResponse extends string, TUnauthorizedBody>(definition: BearerAuthDefinition<TContribution, TResponse, TUnauthorizedBody>): HttpMiddleware<TContribution, HttpMiddlewareContext, AuthenticationShortCircuit<TResponse, TUnauthorizedBody>>;
46
+ export {};
@@ -0,0 +1,96 @@
1
+ import { defineLayer } from '../core/index.js';
2
+ export function basicAuth(definition) {
3
+ const challenge = formatBasicChallenge(definition.realm);
4
+ return defineLayer({
5
+ name: definition.name ?? 'basicAuth',
6
+ factory: () => {
7
+ const runtime = definition.factory();
8
+ return async (context, next) => {
9
+ const credentials = decodeBasicCredentials(context.request.headers.get('authorization'));
10
+ if (!credentials) {
11
+ return authenticationFailure(runtime.unauthorized(), challenge);
12
+ }
13
+ const contribution = await runtime.authenticate(credentials);
14
+ if (contribution == null) {
15
+ return authenticationFailure(runtime.unauthorized(), challenge);
16
+ }
17
+ await next(contribution);
18
+ };
19
+ },
20
+ });
21
+ }
22
+ export function bearerAuth(definition) {
23
+ const challenge = formatBearerChallenge(definition.realm);
24
+ return defineLayer({
25
+ name: definition.name ?? 'bearerAuth',
26
+ factory: () => {
27
+ const runtime = definition.factory();
28
+ return async (context, next) => {
29
+ const token = readBearerToken(context.request.headers.get('authorization'));
30
+ if (!token) {
31
+ return authenticationFailure(runtime.unauthorized(), challenge);
32
+ }
33
+ const contribution = await runtime.authenticate(token);
34
+ if (contribution == null) {
35
+ return authenticationFailure(runtime.unauthorized(), challenge);
36
+ }
37
+ await next(contribution);
38
+ };
39
+ },
40
+ });
41
+ }
42
+ function authenticationFailure(failure, challenge) {
43
+ return {
44
+ kind: 'http-result',
45
+ response: failure.response,
46
+ body: failure.body,
47
+ headers: { 'www-authenticate': challenge },
48
+ };
49
+ }
50
+ function formatBasicChallenge(realm) {
51
+ assertValidRealm(realm, 'Basic');
52
+ return `Basic realm="${escapeChallengeValue(realm)}", charset="UTF-8"`;
53
+ }
54
+ function formatBearerChallenge(realm) {
55
+ assertValidRealm(realm, 'Bearer');
56
+ return `Bearer realm="${escapeChallengeValue(realm)}"`;
57
+ }
58
+ function assertValidRealm(realm, scheme) {
59
+ if (realm.length === 0 || containsControlCharacter(realm)) {
60
+ throw new TypeError(`${scheme} authentication realm cannot be empty or contain control characters`);
61
+ }
62
+ }
63
+ function containsControlCharacter(value) {
64
+ for (const character of value) {
65
+ const code = character.charCodeAt(0);
66
+ if (code <= 0x1f || code === 0x7f)
67
+ return true;
68
+ }
69
+ return false;
70
+ }
71
+ function decodeBasicCredentials(authorization) {
72
+ const encoded = /^Basic +([A-Za-z0-9+/]+={0,2})$/i.exec(authorization ?? '')?.[1];
73
+ if (!encoded)
74
+ return undefined;
75
+ try {
76
+ const binary = atob(encoded);
77
+ const bytes = Uint8Array.from(binary, (character) => character.charCodeAt(0));
78
+ const decoded = new TextDecoder('utf-8', { fatal: true }).decode(bytes);
79
+ const separator = decoded.indexOf(':');
80
+ if (separator < 0)
81
+ return undefined;
82
+ return {
83
+ username: decoded.slice(0, separator),
84
+ password: decoded.slice(separator + 1),
85
+ };
86
+ }
87
+ catch {
88
+ return undefined;
89
+ }
90
+ }
91
+ function readBearerToken(authorization) {
92
+ return /^Bearer +([^\s]+)$/i.exec(authorization ?? '')?.[1];
93
+ }
94
+ function escapeChallengeValue(value) {
95
+ return value.replaceAll('\\', '\\\\').replaceAll('"', '\\"');
96
+ }
@@ -1,13 +1,12 @@
1
- import { type ContractBinding, type ContractDefinition, type ContractOfBinding, type ProcedureDefinition, type SchemaInput, type SchemaOutput, type StandardSchemaV1 } from '../core/index.js';
2
- import type { HttpHeaders, HttpParamsSchemas, HttpProtocol, HttpProtocolDefinition, HttpRequestBodyDefinition, HttpResponseDefinition } from './definitions.js';
3
- import { type PathParamNames, type RawPathParams } from './path.js';
1
+ import { type SchemaInput, type SchemaOutput, type StandardSchemaV1 } from '../core/index.js';
2
+ import type { HttpContract, HttpExecutionResponseDefinition, HttpExecutionRouteDefinition, HttpHeaders } from './extension.js';
3
+ import { type PathParamNames } from './path.js';
4
4
  export interface HttpClientTransportRequest {
5
5
  readonly method: string;
6
6
  readonly path: string;
7
7
  readonly query?: unknown;
8
8
  readonly headers?: unknown;
9
9
  readonly body?: unknown;
10
- readonly contentType?: string;
11
10
  }
12
11
  export interface HttpClientTransportResponse {
13
12
  readonly status: number;
@@ -15,45 +14,43 @@ export interface HttpClientTransportResponse {
15
14
  readonly headers?: HttpHeaders;
16
15
  }
17
16
  export type HttpClientTransport = (request: HttpClientTransportRequest) => Promise<HttpClientTransportResponse>;
18
- type HttpProtocolOf<TProcedure extends ProcedureDefinition> = TProcedure['protocols'] extends {
19
- readonly http: infer TProtocol;
20
- } ? TProtocol extends HttpProtocol<infer TDefinition> ? HttpProtocol<TDefinition> : never : never;
21
- type HttpProcedureNames<TContract extends ContractDefinition> = {
22
- [TProcedure in keyof TContract['procedures'] & string]: HttpProtocolOf<TContract['procedures'][TProcedure]> extends never ? never : TProcedure;
23
- }[keyof TContract['procedures'] & string];
24
- type HttpDefinitionOf<TContract extends ContractDefinition, TProcedure extends HttpProcedureNames<TContract>> = HttpProtocolOf<TContract['procedures'][TProcedure]>['definition'];
25
- type RequestBodySchema<TBody> = TBody extends HttpRequestBodyDefinition<infer TSchema> ? TSchema : never;
26
- type PathParamsInput<TDefinition extends HttpProtocolDefinition> = PathParamNames<TDefinition['path']> extends never ? never : TDefinition['request'] extends {
27
- readonly params: infer TSchemas extends HttpParamsSchemas;
17
+ type PathParamsInput<TDefinition extends HttpExecutionRouteDefinition> = PathParamNames<TDefinition['path']> extends never ? never : TDefinition['request'] extends {
18
+ readonly params: infer TSchemas extends Readonly<Record<string, StandardSchemaV1>>;
28
19
  } ? Readonly<{
29
20
  [TName in keyof TSchemas]: SchemaInput<TSchemas[TName]>;
30
- }> : RawPathParams<TDefinition['path']>;
31
- type RequestPartInput<TDefinition extends HttpProtocolDefinition, TPart extends 'query' | 'headers'> = TDefinition['request'] extends infer TRequest ? TRequest extends Record<TPart, infer TSchema extends StandardSchemaV1> ? SchemaInput<TSchema> : never : never;
32
- type RequestBodyInput<TDefinition extends HttpProtocolDefinition> = TDefinition['request'] extends {
33
- readonly body: infer TBody extends HttpRequestBodyDefinition;
34
- } ? SchemaInput<RequestBodySchema<TBody>> : never;
21
+ }> : Readonly<{
22
+ [TName in PathParamNames<TDefinition['path']>]: string;
23
+ }>;
24
+ type RequestPartInput<TDefinition extends HttpExecutionRouteDefinition, TPart extends 'query' | 'headers'> = TDefinition['request'] extends infer TRequest ? TRequest extends Record<TPart, infer TSchema extends StandardSchemaV1> ? SchemaInput<TSchema> : never : never;
25
+ type RequestBodyInput<TDefinition extends HttpExecutionRouteDefinition> = TDefinition['request'] extends {
26
+ readonly body: infer TBody extends StandardSchemaV1;
27
+ } ? SchemaInput<TBody> : never;
35
28
  type RequestField<TName extends string, TValue> = [TValue] extends [never] ? object : undefined extends TValue ? {
36
29
  readonly [TKey in TName]?: Exclude<TValue, undefined>;
37
30
  } : {
38
31
  readonly [TKey in TName]: TValue;
39
32
  };
40
- export type HttpClientRequest<TDefinition extends HttpProtocolDefinition> = RequestField<'params', PathParamsInput<TDefinition>> & RequestField<'query', RequestPartInput<TDefinition, 'query'>> & RequestField<'headers', RequestPartInput<TDefinition, 'headers'>> & RequestField<'body', RequestBodyInput<TDefinition>>;
41
- type ResponseBodyOutput<TResponse extends HttpResponseDefinition> = TResponse extends {
33
+ export type HttpClientRequest<TDefinition extends HttpExecutionRouteDefinition> = RequestField<'params', PathParamsInput<TDefinition>> & RequestField<'query', RequestPartInput<TDefinition, 'query'>> & RequestField<'headers', RequestPartInput<TDefinition, 'headers'>> & RequestField<'body', RequestBodyInput<TDefinition>>;
34
+ type ResponseBodyOutput<TResponse extends HttpExecutionResponseDefinition> = TResponse extends {
42
35
  readonly stream: 'server';
43
- } ? AsyncIterable<SchemaOutput<TResponse['body']>> : SchemaOutput<TResponse['body']>;
44
- type ResponseHeadersOutput<TResponse extends HttpResponseDefinition> = TResponse extends {
45
- readonly headers: infer THeaders extends StandardSchemaV1;
46
- } ? SchemaOutput<THeaders> : HttpHeaders;
47
- export type HttpClientResponse<TDefinition extends HttpProtocolDefinition> = {
48
- [TVariant in keyof TDefinition['responses'] & string]: NonNullable<TDefinition['responses'][TVariant]> extends infer TResponse extends HttpResponseDefinition ? {
36
+ readonly body: infer TBody extends StandardSchemaV1;
37
+ } ? AsyncIterable<SchemaOutput<TBody>> : TResponse['body'] extends StandardSchemaV1 ? SchemaOutput<TResponse['body']> : undefined;
38
+ type ResponseHeadersSchema<TResponse extends HttpExecutionResponseDefinition> = TResponse extends {
39
+ readonly headers: infer THeaders;
40
+ } ? THeaders extends StandardSchemaV1 ? THeaders : THeaders extends {
41
+ readonly schema: infer TSchema extends StandardSchemaV1;
42
+ } ? TSchema : never : never;
43
+ type ResponseHeadersOutput<TResponse extends HttpExecutionResponseDefinition> = ResponseHeadersSchema<TResponse> extends infer TSchema ? [TSchema] extends [never] ? HttpHeaders : TSchema extends StandardSchemaV1 ? SchemaOutput<TSchema> : HttpHeaders : HttpHeaders;
44
+ export type HttpClientResponse<TDefinition extends HttpExecutionRouteDefinition> = {
45
+ [TVariant in keyof TDefinition['responses'] & string]: TDefinition['responses'][TVariant] extends infer TResponse extends HttpExecutionResponseDefinition ? {
49
46
  readonly status: TResponse['status'];
50
47
  readonly body: ResponseBodyOutput<TResponse>;
51
48
  readonly headers: ResponseHeadersOutput<TResponse>;
52
49
  } : never;
53
50
  }[keyof TDefinition['responses'] & string];
54
- type HttpClientMethod<TDefinition extends HttpProtocolDefinition> = keyof HttpClientRequest<TDefinition> extends never ? () => Promise<HttpClientResponse<TDefinition>> : (request: HttpClientRequest<TDefinition>) => Promise<HttpClientResponse<TDefinition>>;
55
- export type HttpClient<TBinding extends ContractBinding> = {
56
- readonly [TProcedure in HttpProcedureNames<ContractOfBinding<TBinding>>]: HttpClientMethod<HttpDefinitionOf<ContractOfBinding<TBinding>, TProcedure>>;
51
+ type HttpClientMethod<TDefinition extends HttpExecutionRouteDefinition> = keyof HttpClientRequest<TDefinition> extends never ? () => Promise<HttpClientResponse<TDefinition>> : (request: HttpClientRequest<TDefinition>) => Promise<HttpClientResponse<TDefinition>>;
52
+ export type HttpClient<TContract extends HttpContract> = {
53
+ readonly [TName in keyof TContract['routes']]: HttpClientMethod<TContract['routes'][TName]>;
57
54
  };
58
55
  export declare class HttpClientResponseError extends Error {
59
56
  readonly status: number;
@@ -62,7 +59,7 @@ export declare class HttpClientResponseError extends Error {
62
59
  readonly procedure: string;
63
60
  constructor(status: number, method: string, path: string, procedure: string, message: string, options?: ErrorOptions);
64
61
  }
65
- export declare function createHttpClient<const TBinding extends ContractBinding>(binding: TBinding, transport: HttpClientTransport): HttpClient<TBinding>;
62
+ export declare function createHttpClient<const TContract extends HttpContract>(contract: TContract, transport: HttpClientTransport): HttpClient<TContract>;
66
63
  export interface FetchHttpTransportOptions {
67
64
  readonly baseUrl: string | URL;
68
65
  readonly fetch?: typeof globalThis.fetch;