@occultist/occultist 0.0.4 → 0.0.6

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 (82) hide show
  1. package/dist/accept.js +0 -1
  2. package/dist/actions/actionSets.d.ts +3 -3
  3. package/dist/actions/actions.d.ts +69 -48
  4. package/dist/actions/actions.js +39 -4
  5. package/dist/actions/context.d.ts +15 -11
  6. package/dist/actions/context.js +5 -0
  7. package/dist/actions/meta.d.ts +18 -12
  8. package/dist/actions/meta.js +114 -38
  9. package/dist/actions/spec.d.ts +3 -3
  10. package/dist/actions/types.d.ts +45 -16
  11. package/dist/actions/writer.d.ts +1 -1
  12. package/dist/actions/writer.test.js +2 -2
  13. package/dist/cache/cache.d.ts +3 -3
  14. package/dist/cache/cache.js +111 -42
  15. package/dist/cache/etag.test.js +1 -1
  16. package/dist/cache/file.d.ts +33 -1
  17. package/dist/cache/file.js +92 -10
  18. package/dist/cache/memory.d.ts +12 -2
  19. package/dist/cache/memory.js +63 -1
  20. package/dist/cache/types.d.ts +51 -22
  21. package/dist/errors.d.ts +1 -1
  22. package/dist/jsonld.d.ts +1 -1
  23. package/dist/makeTypeDefs.d.ts +2 -2
  24. package/dist/mod.d.ts +17 -15
  25. package/dist/mod.js +17 -15
  26. package/dist/processAction.d.ts +2 -2
  27. package/dist/processAction.js +1 -1
  28. package/dist/registry.d.ts +74 -8
  29. package/dist/registry.js +70 -8
  30. package/dist/registry.test.js +1 -1
  31. package/dist/scopes.d.ts +8 -8
  32. package/dist/scopes.js +8 -5
  33. package/dist/utils/contextBuilder.d.ts +1 -1
  34. package/dist/utils/getActionContext.d.ts +2 -2
  35. package/dist/utils/getPropertyValueSpecifications.d.ts +1 -1
  36. package/dist/utils/getRequestBodyValues.d.ts +3 -3
  37. package/dist/utils/getRequestIRIValues.d.ts +2 -2
  38. package/dist/utils/isPopulatedObject.js +1 -1
  39. package/dist/utils/makeAppendProblemDetails.d.ts +1 -1
  40. package/dist/utils/makeURLPattern.js +1 -0
  41. package/dist/utils/parseSearchParams.d.ts +2 -2
  42. package/dist/validators.d.ts +2 -2
  43. package/dist/validators.js +2 -2
  44. package/lib/accept.test.ts +1 -1
  45. package/lib/accept.ts +0 -2
  46. package/lib/actions/actionSets.ts +4 -4
  47. package/lib/actions/actions.ts +159 -99
  48. package/lib/actions/context.ts +22 -10
  49. package/lib/actions/meta.ts +140 -55
  50. package/lib/actions/path.test.ts +1 -1
  51. package/lib/actions/path.ts +1 -1
  52. package/lib/actions/spec.ts +3 -3
  53. package/lib/actions/types.ts +60 -15
  54. package/lib/actions/writer.test.ts +2 -2
  55. package/lib/actions/writer.ts +1 -1
  56. package/lib/cache/cache.ts +138 -52
  57. package/lib/cache/etag.test.ts +1 -1
  58. package/lib/cache/file.ts +113 -12
  59. package/lib/cache/memory.ts +85 -3
  60. package/lib/cache/types.ts +70 -23
  61. package/lib/errors.ts +1 -1
  62. package/lib/jsonld.ts +1 -1
  63. package/lib/makeTypeDefs.ts +5 -5
  64. package/lib/mod.ts +17 -15
  65. package/lib/processAction.ts +14 -14
  66. package/lib/registry.test.ts +1 -1
  67. package/lib/registry.ts +96 -19
  68. package/lib/request.ts +1 -1
  69. package/lib/scopes.test.ts +2 -2
  70. package/lib/scopes.ts +14 -11
  71. package/lib/utils/contextBuilder.ts +3 -3
  72. package/lib/utils/getActionContext.ts +4 -4
  73. package/lib/utils/getInternalName.ts +1 -1
  74. package/lib/utils/getPropertyValueSpecifications.ts +4 -4
  75. package/lib/utils/getRequestBodyValues.ts +5 -5
  76. package/lib/utils/getRequestIRIValues.ts +4 -4
  77. package/lib/utils/isPopulatedObject.ts +1 -1
  78. package/lib/utils/makeAppendProblemDetails.ts +1 -1
  79. package/lib/utils/makeURLPattern.ts +1 -0
  80. package/lib/utils/parseSearchParams.ts +2 -2
  81. package/lib/validators.ts +5 -5
  82. package/package.json +4 -2
@@ -1,8 +1,8 @@
1
- import type {HandlerDefinition} from "../mod.js";
2
- import type {Registry} from "../registry.js";
3
- import type {ActionPayload, ActionSpec, ContextState, ParsedIRIValues} from "./spec.js";
4
- import type {ImplementedAction} from "./types.js";
5
- import type {ResponseBody} from "./writer.js";
1
+ import type {HandlerDefinition} from "../mod.ts";
2
+ import type {Registry} from "../registry.ts";
3
+ import type {ActionPayload, ActionSpec, ContextState, ParsedIRIValues} from "./spec.ts";
4
+ import type {AuthState, ImplementedAction} from "./types.ts";
5
+ import type {ResponseBody} from "./writer.ts";
6
6
 
7
7
 
8
8
  class EditableContext {
@@ -12,22 +12,26 @@ class EditableContext {
12
12
  body?: ResponseBody;
13
13
  };
14
14
 
15
- export type CacheContextArgs = {
15
+ export type CacheContextArgs<
16
+ Auth extends AuthState = AuthState,
17
+ > = {
16
18
  req: Request;
17
19
  url: string;
18
20
  contentType: string;
19
21
  public: boolean;
20
22
  authKey?: string;
23
+ auth: Auth;
21
24
  handler: HandlerDefinition;
22
25
  params: ParsedIRIValues;
23
26
  query: ParsedIRIValues;
24
27
  };
25
28
 
26
-
27
29
  /**
28
30
  * Request context object.
29
31
  */
30
- export class CacheContext {
32
+ export class CacheContext<
33
+ Auth extends AuthState = AuthState,
34
+ > {
31
35
  #editable = new EditableContext();
32
36
  req: Request;
33
37
  method: string;
@@ -35,18 +39,20 @@ export class CacheContext {
35
39
  contentType: string;
36
40
  public: boolean = false
37
41
  authKey?: string;
42
+ auth: Auth;
38
43
  action: ImplementedAction;
39
44
  registry: Registry;
40
45
  params: ParsedIRIValues;
41
46
  query: ParsedIRIValues;
42
47
  headers: Headers = new Headers();
43
48
 
44
- constructor(args: CacheContextArgs) {
49
+ constructor(args: CacheContextArgs<Auth>) {
45
50
  this.req = args.req;
46
51
  this.url = args.url;
47
52
  this.contentType = args.contentType;
48
53
  this.public = args.public;
49
54
  this.authKey = args.authKey;
55
+ this.auth = args.auth;
50
56
  this.action = args.handler.action;
51
57
  this.method = args.handler.action.method;
52
58
  this.registry = args.handler.action.registry;
@@ -96,6 +102,7 @@ export class CacheContext {
96
102
 
97
103
  export type ContextArgs<
98
104
  State extends ContextState = ContextState,
105
+ Auth extends AuthState = AuthState,
99
106
  Spec extends ActionSpec = ActionSpec,
100
107
  > = {
101
108
  req: Request;
@@ -103,6 +110,7 @@ export type ContextArgs<
103
110
  contentType: string;
104
111
  public: boolean;
105
112
  authKey?: string;
113
+ auth: Auth;
106
114
  handler: HandlerDefinition<State, Spec>;
107
115
  params: ParsedIRIValues;
108
116
  query: ParsedIRIValues;
@@ -114,6 +122,7 @@ export type ContextArgs<
114
122
  */
115
123
  export class Context<
116
124
  State extends ContextState = ContextState,
125
+ Auth extends AuthState = AuthState,
117
126
  Spec extends ActionSpec = ActionSpec,
118
127
  > {
119
128
  #editable = new EditableContext();
@@ -123,6 +132,7 @@ export class Context<
123
132
  contentType: string;
124
133
  public: boolean = false
125
134
  authKey?: string;
135
+ auth: Auth;
126
136
  state: State = {} as State;
127
137
  action: ImplementedAction<State, Spec>;
128
138
  registry: Registry;
@@ -131,12 +141,13 @@ export class Context<
131
141
  payload: ActionPayload<Spec>;
132
142
  headers: Headers = new Headers();
133
143
 
134
- constructor(args: ContextArgs<State, Spec>) {
144
+ constructor(args: ContextArgs<State, Auth, Spec>) {
135
145
  this.req = args.req;
136
146
  this.url = args.url;
137
147
  this.contentType = args.contentType;
138
148
  this.public = args.public;
139
149
  this.authKey = args.authKey;
150
+ this.auth = args.auth;
140
151
  this.action = args.handler.action;
141
152
  this.method = args.handler.action.method;
142
153
  this.registry = args.handler.action.registry;
@@ -145,6 +156,7 @@ export class Context<
145
156
  this.payload = args.payload;
146
157
 
147
158
  Object.freeze(this);
159
+ Object.freeze(this.auth);
148
160
  }
149
161
 
150
162
  get status(): undefined | number {
@@ -1,16 +1,20 @@
1
- import {CacheMiddleware} from '../cache/cache.js';
2
- import {CacheEntryDescriptor, CacheInstanceArgs} from '../cache/types.js';
3
- import {JSONValue} from '../jsonld.js';
4
- import {processAction} from '../processAction.js';
5
- import type {Registry} from '../registry.js';
6
- import type {Scope} from "../scopes.js";
7
- import {joinPaths} from '../utils/joinPaths.js';
8
- import {HandlerDefinition} from './actions.js';
9
- import {CacheContext, Context} from './context.js';
10
- import {Path} from "./path.js";
11
- import type {ActionSpec, ContextState, FileValue, NextFn, TransformerFn} from './spec.js';
12
- import type {HintArgs, ImplementedAction} from './types.js';
13
- import type {HTTPWriter, ResponseTypes} from "./writer.js";
1
+ import {Accept} from '../accept.ts';
2
+ import {CacheMiddleware} from '../cache/cache.ts';
3
+ import type {CacheEntryDescriptor, CacheInstanceArgs, CacheSemantics} from '../cache/types.ts';
4
+ import {ProblemDetailsError} from '../errors.ts';
5
+ import type {JSONValue} from '../jsonld.ts';
6
+ import {processAction, type ProcessActionResult} from '../processAction.ts';
7
+ import type {Registry} from '../registry.ts';
8
+ import {WrappedRequest} from '../request.ts';
9
+ import type {Scope} from "../scopes.ts";
10
+ import {joinPaths} from '../utils/joinPaths.ts';
11
+ import {HandlerDefinition} from './actions.ts';
12
+ import {ActionSet} from './actionSets.ts';
13
+ import {CacheContext, Context} from './context.ts';
14
+ import {Path} from "./path.ts";
15
+ import type {ActionSpec, ContextState, FileValue, NextFn, TransformerFn} from './spec.ts';
16
+ import type {AuthMiddleware, AuthState, CacheHitHeader, HintArgs, ImplementedAction} from './types.ts';
17
+ import {ResponseWriter, type HTTPWriter, type ResponseTypes} from "./writer.ts";
14
18
 
15
19
 
16
20
  export const BeforeDefinition = 0;
@@ -20,6 +24,7 @@ const cacheMiddleware = new CacheMiddleware();
20
24
 
21
25
  export class ActionMeta<
22
26
  State extends ContextState = ContextState,
27
+ Auth extends AuthState = AuthState,
23
28
  Spec extends ActionSpec = ActionSpec,
24
29
  > {
25
30
  rootIRI: string;
@@ -38,6 +43,7 @@ export class ActionMeta<
38
43
  acceptCache = new Set<string>();
39
44
  compressBeforeCache: boolean = false;
40
45
  cacheOccurance: 0 | 1 = BeforeDefinition;
46
+ auth?: AuthMiddleware<Auth>;
41
47
  cache: CacheInstanceArgs[] = [];
42
48
  serverTiming: boolean = false;
43
49
 
@@ -67,18 +73,44 @@ export class ActionMeta<
67
73
  this.#setAcceptCache();
68
74
  }
69
75
 
76
+ async perform(req: Request): Promise<Response> {
77
+ const actionSet = new ActionSet(this.rootIRI, this.method, this.path.normalized, [this]);
78
+ const wrapped = new WrappedRequest(this.rootIRI, req);
79
+ const writer = new ResponseWriter();
80
+ const accept = Accept.from(req);
81
+ const url = new URL(wrapped.url);
82
+ const result = actionSet.matches(wrapped.method, url.pathname, accept);
83
+
84
+ if (result.type === 'match') {
85
+ const handler = this.action.handlerFor(result.contentType);
86
+
87
+ return this.handleRequest({
88
+ startTime: performance.now(),
89
+ contentType: result.contentType,
90
+ url: url.toString(),
91
+ req: wrapped,
92
+ writer,
93
+ spec: this.action.spec as Spec,
94
+ handler,
95
+ }) as Promise<Response>;
96
+ }
97
+
98
+ return new Response(null, { status: 404 });
99
+ }
100
+
101
+ /**
102
+ *
103
+ */
70
104
  async handleRequest({
71
- startTime,
72
105
  contentType,
73
- language: _language,
74
- encoding: _encoding,
75
106
  url,
76
107
  req,
77
108
  writer,
78
109
  spec,
79
110
  handler,
111
+ cacheHitHeader,
112
+ startTime,
80
113
  }: {
81
- startTime: number;
82
114
  contentType?: string;
83
115
  language?: string;
84
116
  encoding?: string;
@@ -87,22 +119,28 @@ export class ActionMeta<
87
119
  writer: HTTPWriter;
88
120
  spec?: Spec;
89
121
  handler?: HandlerDefinition<State, Spec>,
122
+ cacheHitHeader?: CacheHitHeader;
123
+ startTime?: number;
90
124
  }): Promise<ResponseTypes> {
91
125
  const state: State = {} as State;
92
126
  const headers = new Headers();
93
- let ctx: CacheContext | Context<State, Spec>;
94
- let cacheCtx: CacheContext;
127
+
128
+ let authKey: string | undefined;
129
+ let auth: Auth = {} as Auth;
130
+ let ctx: CacheContext<Auth> | Context<State, Auth, Spec>;
131
+ let cacheCtx: CacheContext<Auth>;
95
132
  let prevTime = startTime;
133
+ let performServerTiming = this.serverTiming && startTime != null;
96
134
 
97
135
  const serverTiming = (name: string) => {
98
136
  const nextTime = performance.now();
99
137
  const duration = nextTime - prevTime;
100
138
 
101
- headers.append('Server-Timing', `${name};dur=${duration.toPrecision(2)}`);
139
+ headers.append('Server-Timing', `${name};dur=${duration.toFixed(2)}`);
102
140
  prevTime = nextTime;
103
141
  }
104
142
 
105
- if (this.serverTiming) serverTiming('enter');
143
+ if (performServerTiming) serverTiming('enter');
106
144
 
107
145
  // add auth check
108
146
  if (this.hints.length !== 0) {
@@ -113,72 +151,86 @@ export class ActionMeta<
113
151
 
114
152
  let next: NextFn = async () => {
115
153
  if (typeof handler.handler === 'function') {
116
- await handler.handler(ctx as Context<State, Spec>);
154
+ await handler.handler(ctx as Context<State, Auth, Spec>);
117
155
  } else {
118
156
  ctx.status = 200;
119
157
  ctx.body = handler.handler;
120
158
  }
121
159
 
122
- if (this.serverTiming) serverTiming('handle');
160
+ if (performServerTiming) serverTiming('handle');
123
161
  };
124
162
 
125
163
  {
126
164
  const upstream: NextFn = next;
127
165
  next = async () => {
128
- const res = await processAction<State, Spec>({
129
- iri: url,
130
- req,
131
- spec: spec ?? {} as Spec,
132
- state,
133
- action: this.action,
134
- });
166
+ let processed: ProcessActionResult<Spec>;
167
+
168
+ if (spec != null) {
169
+ processed = await processAction<State, Spec>({
170
+ iri: url,
171
+ req,
172
+ spec: spec ?? {} as Spec,
173
+ state,
174
+ action: this.action,
175
+ });
176
+ }
135
177
 
136
- ctx = new Context<State, Spec>({
178
+ ctx = new Context<State, Auth, Spec>({
137
179
  req,
138
180
  url,
139
181
  contentType,
140
- public: this.public,
182
+ public: this.public && authKey == null,
183
+ auth,
184
+ authKey,
141
185
  handler,
142
- params: res.params,
143
- query: res.query,
144
- payload: res.payload,
186
+ params: processed.params ?? {},
187
+ query: processed.query ?? {},
188
+ payload: processed.payload ?? {} as ProcessActionResult<Spec>['payload'],
145
189
  });
146
190
 
147
191
  if (contentType != null) {
148
192
  ctx.headers.set('Content-Type', contentType)
149
193
  }
150
194
 
151
- if (this.serverTiming) serverTiming('payload');
195
+ if (performServerTiming) serverTiming('payload');
152
196
 
153
197
  await upstream();
154
198
  }
155
199
  }
156
200
 
157
201
  if (this.cache.length > 0) {
158
- cacheCtx = new CacheContext({
159
- req,
160
- url,
161
- contentType,
162
- public: this.public,
163
- handler,
164
- params: {},
165
- query: {},
166
- });
167
- const descriptors: CacheEntryDescriptor[] = this.cache.map(args => {
168
- return {
169
- contentType,
170
- action: this.action as ImplementedAction,
171
- request: req,
172
- args,
173
- };
174
- });
175
-
176
202
  const upstream = next;
177
203
  next = async () => {
204
+ cacheCtx = new CacheContext({
205
+ req,
206
+ url,
207
+ contentType,
208
+ public: this.public && authKey == null,
209
+ auth,
210
+ authKey,
211
+ handler,
212
+ params: {},
213
+ query: {},
214
+ });
215
+ const descriptors: CacheEntryDescriptor[] = this.cache.map(args => {
216
+ return {
217
+ contentType,
218
+ semantics: args.semantics ?? req.method.toLowerCase() as CacheSemantics,
219
+ action: this.action as ImplementedAction,
220
+ request: req,
221
+ args,
222
+ };
223
+ });
224
+
178
225
  await cacheMiddleware.use(
179
226
  descriptors,
180
227
  cacheCtx,
181
228
  async () => {
229
+ // write any cache headers to the response headers.
230
+ // this should be reviewed as it may be unsafe to
231
+ // allow a handler to override these headers.
232
+ writer.mergeHeaders(cacheCtx.headers);
233
+
182
234
  // cache was not hit if in this function
183
235
  await upstream();
184
236
 
@@ -202,11 +254,44 @@ export class ActionMeta<
202
254
  }
203
255
  }
204
256
 
257
+ if (this.auth != null) {
258
+ const upstream = next;
259
+ next = async () => {
260
+ const res = await this.auth(req);
261
+
262
+ if (Array.isArray(res) &&
263
+ typeof res[0] === 'string' &&
264
+ res.length > 0) {
265
+ authKey = res[0];
266
+ auth = res[1];
267
+
268
+ await upstream();
269
+ } else if (this.public) {
270
+ await upstream();
271
+ } else {
272
+
273
+ // Failed authentication on a private endpoint.
274
+ throw new ProblemDetailsError(404, {
275
+ title: 'Not found',
276
+ });
277
+ }
278
+ };
279
+ }
280
+
281
+
205
282
  try {
206
283
  await next();
207
284
 
208
285
  if (cacheCtx?.hit) {
209
- if (this.serverTiming) serverTiming('hit');
286
+ if (performServerTiming) serverTiming('hit');
287
+
288
+ if (Array.isArray(cacheHitHeader)) {
289
+ cacheCtx.headers.set(cacheHitHeader[0], cacheHitHeader[1]);
290
+ } else if (typeof cacheHitHeader === 'string') {
291
+ cacheCtx.headers.set(cacheHitHeader, 'HIT');
292
+ } else if (cacheHitHeader) {
293
+ cacheCtx.headers.set('X-Cache', 'HIT');
294
+ }
210
295
 
211
296
  // set the ctx so the writer has access to the cached values.
212
297
  ctx = cacheCtx;
@@ -1,6 +1,6 @@
1
1
  import test from 'node:test';
2
2
  import assert from "node:assert";
3
- import { Path } from "./path.js";
3
+ import { Path } from "./path.ts";
4
4
 
5
5
 
6
6
  test('URI template style path creates valid URL Pattern', () => {
@@ -1,4 +1,4 @@
1
- import { makeURLPattern } from "../utils/makeURLPattern.js";
1
+ import { makeURLPattern } from "../utils/makeURLPattern.ts";
2
2
 
3
3
  const paramsRe = /((?<s>[^\{\}]+)|({(?<t>[\?\#])?(?<v>[^}]+)}))/g
4
4
 
@@ -1,6 +1,6 @@
1
- import type { JSONPrimitive, JSONValue, OrArray, RecursiveDigit, RecursiveIncrement, TypeDef } from "../jsonld.js";
2
- import {Action} from "./actions.js";
3
- import type { Context } from './context.js';
1
+ import type { JSONPrimitive, JSONValue, OrArray, RecursiveDigit, RecursiveIncrement, TypeDef } from "../jsonld.ts";
2
+ import {Action} from "./actions.ts";
3
+ import type { Context } from './context.ts';
4
4
 
5
5
  export type EmptyState = Record<string, unknown>;
6
6
  export type EmptySpec = Map<PropertyKey, never>;
@@ -1,11 +1,40 @@
1
- import type { HTTPWriter } from "./writer.js";
2
- import type { Registry } from '../registry.js';
3
- import type { Scope } from "../scopes.js";
4
- import type { ContextState, ActionSpec } from "./spec.js";
5
- import type { Context } from "./context.js";
1
+ import type { HTTPWriter } from "./writer.ts";
2
+ import type { Registry } from '../registry.ts';
3
+ import type { Scope } from "../scopes.ts";
4
+ import type { ContextState, ActionSpec } from "./spec.ts";
5
+ import type { Context } from "./context.ts";
6
6
  import type { ServerResponse } from "node:http";
7
- import type { JSONObject, TypeDef } from "../jsonld.js";
8
- import type {HandlerDefinition} from "../mod.js";
7
+ import type { JSONObject, TypeDef } from "../jsonld.ts";
8
+ import type {HandlerDefinition} from "../mod.ts";
9
+
10
+ export type CacheHitHeader = boolean | string | [header: string, value: string];
11
+
12
+ export type AuthState = Record<string, unknown>;
13
+
14
+ /**
15
+ * Middleware that identifies the authenticating agent from the request
16
+ * and confirms they have access to the resource.
17
+ *
18
+ * When successfully authenticated the middleware should return with
19
+ * an array of two values. The first being a key unique to the user, or
20
+ * group, which can access this resource. This key is used for varying
21
+ * private cache so should alway vary on the user if personalized information
22
+ * would be returned in the response.
23
+ *
24
+ * The second response item is optional and should be an object holding identifying
25
+ * information such as permissions or details of the user or group that might be used
26
+ * when forming the response.
27
+ *
28
+ * @param req The request.
29
+ */
30
+ export type AuthMiddleware<
31
+ Auth extends AuthState = AuthState,
32
+ > = (req: Request) =>
33
+ | void
34
+ | Promise<void>
35
+ | [authKey: string, auth?: Auth]
36
+ | Promise<[authKey: string, auth?: Auth]>
37
+ ;
9
38
 
10
39
  export type HintLink = {
11
40
  href: string;
@@ -57,8 +86,9 @@ export type HandlerValue = Exclude<BodyInit, ReadableStream>;
57
86
  */
58
87
  export type HandlerFn<
59
88
  State extends ContextState = ContextState,
89
+ Auth extends AuthState = AuthState,
60
90
  Spec extends ActionSpec = ActionSpec,
61
- > = (ctx: Context<State, Spec>) => void | Promise<void>;
91
+ > = (ctx: Context<State, Auth, Spec>) => void | Promise<void>;
62
92
 
63
93
  /**
64
94
  * A handler object argument.
@@ -68,10 +98,11 @@ export type HandlerFn<
68
98
  */
69
99
  export interface HandlerObj<
70
100
  State extends ContextState = ContextState,
101
+ Auth extends AuthState = AuthState,
71
102
  Spec extends ActionSpec = ActionSpec,
72
103
  > {
73
104
  contentType: string | string[];
74
- handler: HandlerFn<State, Spec> | HandlerValue;
105
+ handler: HandlerFn<State, Auth, Spec> | HandlerValue;
75
106
  meta?: HandlerMeta;
76
107
  hints?: HintArgs;
77
108
  };
@@ -81,25 +112,28 @@ export interface HandlerObj<
81
112
  */
82
113
  export type HandlerArgs<
83
114
  State extends ContextState = ContextState,
115
+ Auth extends AuthState = AuthState,
84
116
  Spec extends ActionSpec = ActionSpec,
85
117
  > =
86
118
  | HandlerValue
87
- | HandlerFn<State, Spec>
88
- | HandlerObj<State, Spec>
119
+ | HandlerFn<State, Auth, Spec>
120
+ | HandlerObj<State, Auth, Spec>
89
121
  ;
90
122
 
91
123
  export type HandleRequestArgs = {
92
- startTime: number;
93
124
  contentType?: string;
94
125
  language?: string;
95
126
  encoding?: string;
96
127
  url: string;
97
128
  req: Request;
98
129
  writer: HTTPWriter;
130
+ startTime?: number;
131
+ cacheHitHeader?: CacheHitHeader;
99
132
  };
100
133
 
101
134
  export interface ImplementedAction<
102
135
  State extends ContextState = ContextState,
136
+ Auth extends AuthState = AuthState,
103
137
  Spec extends ActionSpec = ActionSpec,
104
138
  > {
105
139
  readonly public: boolean;
@@ -113,16 +147,27 @@ export interface ImplementedAction<
113
147
  readonly spec: Spec;
114
148
  readonly registry: Registry;
115
149
  readonly scope?: Scope;
116
- readonly handlers: HandlerDefinition<State, Spec>[];
150
+ readonly handlers: HandlerDefinition<State, Auth, Spec>[];
117
151
  readonly contentTypes: string[];
118
152
  readonly context: JSONObject;
119
153
 
120
154
  /**
121
- * @todo
122
- *
123
155
  * Creates a URL compatible with this action.
124
156
  */
125
157
  url(): string;
158
+
159
+ /**
160
+ * Retrives the handler configured for the given content type.
161
+ *
162
+ * @param contentType The content type.
163
+ */
164
+ handlerFor(contentType: string): HandlerDefinition<State, Auth, Spec> | undefined;
165
+
166
+ /**
167
+ * Performs this action using the given fetch Request
168
+ * returning a Response.
169
+ */
170
+ perform(req: Request): Promise<Response>;
126
171
 
127
172
  /**
128
173
  * @todo
@@ -1,7 +1,7 @@
1
1
  import {createServer} from 'node:http';
2
2
  import assert from 'node:assert/strict';
3
3
  import test from 'node:test';
4
- import {ResponseWriter} from './writer.js';
4
+ import {ResponseWriter} from './writer.ts';
5
5
  import {AddressInfo} from 'node:net';
6
6
 
7
7
 
@@ -20,7 +20,7 @@ test('Writer writes hints', () => {
20
20
  fetchPriority: 'high',
21
21
  },
22
22
  {
23
- href: 'https://example.com/main.js',
23
+ href: 'https://example.com/main.ts',
24
24
  as: 'script',
25
25
  preload: true,
26
26
  fetchPriority: 'low',
@@ -1,5 +1,5 @@
1
1
  import { ServerResponse } from 'node:http';
2
- import type { HintLink, HintArgs, HintObj } from './types.js';
2
+ import type { HintLink, HintArgs, HintObj } from './types.ts';
3
3
 
4
4
 
5
5
  function isHintLink(hint: HintObj | HintLink): hint is HintLink {