@occultist/occultist 0.0.8 → 0.0.10

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.
@@ -1,4 +1,4 @@
1
- import type { CacheOperation, HandlerDefinition } from "../mod.ts";
1
+ import type { CacheOperation, HandlerDefinition, StaticAsset } from "../mod.ts";
2
2
  import type { Registry } from "../registry.ts";
3
3
  import type { ActionPayload, ActionSpec, ContextState, ParsedIRIValues } from "./spec.ts";
4
4
  import type { AuthState, ImplementedAction } from "./types.ts";
@@ -84,5 +84,14 @@ export declare class Context<State extends ContextState = ContextState, Auth ext
84
84
  set status(status: number);
85
85
  get body(): undefined | ResponseBody;
86
86
  set body(body: ResponseBody);
87
+ /**
88
+ * Returns the public facing URL of a static asset using its
89
+ * static file alias.
90
+ *
91
+ * @param assetAlias The alias of the static asset.
92
+ * @param cspDirective A directive to add the asset to when generating CSP headers.
93
+ * @returns The public facing URL of the static asset.
94
+ */
95
+ useAsset(assetAlias: string, cspDirective?: string): StaticAsset | undefined;
87
96
  get [Symbol.toStringTag](): string;
88
97
  }
@@ -3,6 +3,8 @@ class EditableContext {
3
3
  etag;
4
4
  status;
5
5
  body;
6
+ staticAssets = new Map();
7
+ cspDirectives;
6
8
  }
7
9
  ;
8
10
  /**
@@ -121,6 +123,33 @@ export class Context {
121
123
  set body(body) {
122
124
  this.#editable.body = body;
123
125
  }
126
+ /**
127
+ * Returns the public facing URL of a static asset using its
128
+ * static file alias.
129
+ *
130
+ * @param assetAlias The alias of the static asset.
131
+ * @param cspDirective A directive to add the asset to when generating CSP headers.
132
+ * @returns The public facing URL of the static asset.
133
+ */
134
+ useAsset(assetAlias, cspDirective) {
135
+ const staticAlias = assetAlias.split('/')[0];
136
+ const extension = this.registry.getStaticExtension(staticAlias);
137
+ if (extension == null)
138
+ return;
139
+ const asset = extension.getAsset(assetAlias);
140
+ if (asset == null)
141
+ return;
142
+ this.#editable.staticAssets.set(asset.alias, asset);
143
+ if (typeof cspDirective === 'string' && cspDirective != null) {
144
+ if (!this.#editable.cspDirectives.has(cspDirective)) {
145
+ this.#editable.cspDirectives.set(cspDirective, [asset.alias]);
146
+ }
147
+ else {
148
+ this.#editable.cspDirectives.get(cspDirective).push(asset.alias);
149
+ }
150
+ }
151
+ return asset;
152
+ }
124
153
  get [Symbol.toStringTag]() {
125
154
  return `action=${this.action.name} method=${this.method} contentType=${this.contentType}`;
126
155
  }
package/dist/mod.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './types.ts';
1
2
  export * from './cache/types.ts';
2
3
  export * from './cache/cache.ts';
3
4
  export * from './cache/memory.ts';
package/dist/mod.js CHANGED
@@ -1,3 +1,4 @@
1
+ export * from "./types.js";
1
2
  export * from "./cache/types.js";
2
3
  export * from "./cache/cache.js";
3
4
  export * from "./cache/memory.js";
@@ -7,6 +7,7 @@ import { IncomingMessage, type ServerResponse } from "node:http";
7
7
  import type { Merge } from "./actions/spec.ts";
8
8
  import type { ContextState, Middleware } from "./actions/spec.ts";
9
9
  import { type CacheOperationResult } from "./mod.ts";
10
+ import type { Extension, StaticExtension } from "./types.ts";
10
11
  export interface Callable<State extends ContextState = ContextState> {
11
12
  method(method: string, name: string, path: string): ActionAuth<State>;
12
13
  }
@@ -248,6 +249,25 @@ export declare class Registry<State extends ContextState = ContextState> impleme
248
249
  * @returns A NodeJS server response instance.
249
250
  */
250
251
  handleRequest(req: IncomingMessage, res: ServerResponse): Promise<ServerResponse>;
252
+ /**
253
+ * Retrieves a static extension by one of the static aliases it uses.
254
+ *
255
+ * @param staticAlias A static alias used to create paths to files served
256
+ * by the static extension.
257
+ */
258
+ getStaticExtension(staticAlias: string): StaticExtension | undefined;
259
+ /**
260
+ * Registers an Occultist extension. This is usually done
261
+ * by extensions when they are created.
262
+ *
263
+ * @param The Occultist extension to register.
264
+ */
265
+ registerExtension(extension: Extension): void;
266
+ /**
267
+ * Must be called after all Occultist extensions have been registered.
268
+ * When some of the extensions have async setup tasks.
269
+ */
270
+ setupExtensions(): Promise<void>;
251
271
  addEventListener(type: RegistryEvents, callback: EventListener): void;
252
272
  removeEventListener(type: RegistryEvents, callback: EventListener): void;
253
273
  }
package/dist/registry.js CHANGED
@@ -122,6 +122,8 @@ export class Registry {
122
122
  #middleware = [];
123
123
  #actions = null;
124
124
  #handlers = null;
125
+ #extensions = [];
126
+ #staticExtensions = new Map();
125
127
  constructor(args) {
126
128
  const url = new URL(args.rootIRI);
127
129
  this.#rootIRI = args.rootIRI;
@@ -499,6 +501,52 @@ export class Registry {
499
501
  return res;
500
502
  }
501
503
  }
504
+ /**
505
+ * Retrieves a static extension by one of the static aliases it uses.
506
+ *
507
+ * @param staticAlias A static alias used to create paths to files served
508
+ * by the static extension.
509
+ */
510
+ getStaticExtension(staticAlias) {
511
+ return this.#staticExtensions.get(staticAlias);
512
+ }
513
+ /**
514
+ * Registers an Occultist extension. This is usually done
515
+ * by extensions when they are created.
516
+ *
517
+ * @param The Occultist extension to register.
518
+ */
519
+ registerExtension(extension) {
520
+ let staticAlias;
521
+ if (typeof extension.getAsset === 'function' &&
522
+ Array.isArray(extension.staticAliases)) {
523
+ for (let i = 0; i < extension.staticAliases.length; i++) {
524
+ staticAlias = extension.staticAliases[i];
525
+ if (this.#staticExtensions.has(staticAlias)) {
526
+ throw new Error(`Static alias '${staticAlias}' already used by other extension`);
527
+ }
528
+ this.#staticExtensions.set(staticAlias, extension);
529
+ }
530
+ }
531
+ this.#extensions.push(extension);
532
+ }
533
+ /**
534
+ * Must be called after all Occultist extensions have been registered.
535
+ * When some of the extensions have async setup tasks.
536
+ */
537
+ async setupExtensions() {
538
+ const setupStreams = [];
539
+ for (let i = 0; i < this.#extensions.length; i++) {
540
+ if (typeof this.#extensions[i].setup === 'function') {
541
+ setupStreams.push(this.#extensions[i].setup());
542
+ }
543
+ }
544
+ for (let i = 0; i < setupStreams.length; i++) {
545
+ for await (const message of setupStreams[i]) {
546
+ console.log(message);
547
+ }
548
+ }
549
+ }
502
550
  addEventListener(type, callback) {
503
551
  this.#eventTarget.addEventListener(type, callback);
504
552
  }
package/dist/types.d.ts CHANGED
@@ -1,3 +1,35 @@
1
+ export interface StaticAsset {
2
+ alias: string;
3
+ contentType: string;
4
+ url: string;
5
+ integrity?: string;
6
+ }
7
+ export interface StaticContext {
8
+ link(alias: string, as: string): string;
9
+ }
10
+ export interface Extension {
11
+ name: string;
12
+ setup?(): ReadableStream;
13
+ getAsset?(): StaticAsset | undefined;
14
+ staticAliases?: string[];
15
+ }
16
+ export interface StaticExtension {
17
+ /**
18
+ * The name of the static extension.
19
+ */
20
+ name: string;
21
+ /**
22
+ * Root level aliases the extension uses to identify
23
+ * assets it manages.
24
+ */
25
+ staticAliases: string[];
26
+ /**
27
+ * Retrieves a static assets from the extension.
28
+ *
29
+ * @param assetAlias The alias for the asset.
30
+ */
31
+ getAsset(assetAlias: string): StaticAsset | undefined;
32
+ }
1
33
  export type ProblemDetailsParam = {
2
34
  name: string;
3
35
  reason: string;
package/dist/types.js CHANGED
@@ -1 +1,3 @@
1
+ ;
2
+ ;
1
3
  export {};
@@ -1,4 +1,4 @@
1
- import type {CacheOperation, HandlerDefinition} from "../mod.ts";
1
+ import type {CacheOperation, HandlerDefinition, StaticAsset} from "../mod.ts";
2
2
  import type {Registry} from "../registry.ts";
3
3
  import type {ActionPayload, ActionSpec, ContextState, ParsedIRIValues} from "./spec.ts";
4
4
  import type {AuthState, ImplementedAction} from "./types.ts";
@@ -10,6 +10,8 @@ class EditableContext {
10
10
  etag?: string;
11
11
  status?: number;
12
12
  body?: ResponseBody;
13
+ staticAssets: Map<string, StaticAsset> = new Map();
14
+ cspDirectives: Map<string, string[]>;
13
15
  };
14
16
 
15
17
  export type CacheContextArgs<
@@ -187,6 +189,37 @@ export class Context<
187
189
  this.#editable.body = body;
188
190
  }
189
191
 
192
+ /**
193
+ * Returns the public facing URL of a static asset using its
194
+ * static file alias.
195
+ *
196
+ * @param assetAlias The alias of the static asset.
197
+ * @param cspDirective A directive to add the asset to when generating CSP headers.
198
+ * @returns The public facing URL of the static asset.
199
+ */
200
+ useAsset(assetAlias: string, cspDirective?: string): StaticAsset | undefined {
201
+ const staticAlias = assetAlias.split('/')[0];
202
+ const extension = this.registry.getStaticExtension(staticAlias);
203
+
204
+ if (extension == null) return;
205
+
206
+ const asset = extension.getAsset(assetAlias);
207
+
208
+ if (asset == null) return;
209
+
210
+ this.#editable.staticAssets.set(asset.alias, asset);
211
+
212
+ if (typeof cspDirective === 'string' && cspDirective != null) {
213
+ if (!this.#editable.cspDirectives.has(cspDirective)) {
214
+ this.#editable.cspDirectives.set(cspDirective, [asset.alias]);
215
+ } else {
216
+ this.#editable.cspDirectives.get(cspDirective).push(asset.alias);
217
+ }
218
+ }
219
+
220
+ return asset;
221
+ }
222
+
190
223
  get [Symbol.toStringTag]() {
191
224
  return `action=${this.action.name} method=${this.method} contentType=${this.contentType}`;
192
225
  }
package/lib/mod.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export * from './types.ts';
1
2
  export * from './cache/types.ts';
2
3
  export * from './cache/cache.ts';
3
4
  export * from './cache/memory.ts';
package/lib/registry.ts CHANGED
@@ -11,6 +11,7 @@ import type { ContextState, Middleware } from "./actions/spec.ts";
11
11
  import {ProblemDetailsError} from "./errors.ts"
12
12
  import {WrappedRequest} from "./request.ts";
13
13
  import {type CacheOperationResult} from "./mod.ts";
14
+ import type {Extension, StaticExtension} from "./types.ts";
14
15
 
15
16
 
16
17
  export interface Callable<
@@ -187,6 +188,8 @@ export class Registry<
187
188
  #middleware: Middleware[] = [];
188
189
  #actions: ImplementedAction[] | null = null;
189
190
  #handlers: HandlerDefinition[] | null = null;
191
+ #extensions: Extension[] = [];
192
+ #staticExtensions: Map<string, StaticExtension> = new Map();
190
193
 
191
194
  constructor(args: RegistryArgs) {
192
195
  const url = new URL(args.rootIRI);
@@ -731,6 +734,62 @@ export class Registry<
731
734
  }
732
735
  }
733
736
 
737
+ /**
738
+ * Retrieves a static extension by one of the static aliases it uses.
739
+ *
740
+ * @param staticAlias A static alias used to create paths to files served
741
+ * by the static extension.
742
+ */
743
+ getStaticExtension(staticAlias: string): StaticExtension | undefined {
744
+ return this.#staticExtensions.get(staticAlias);
745
+ }
746
+
747
+ /**
748
+ * Registers an Occultist extension. This is usually done
749
+ * by extensions when they are created.
750
+ *
751
+ * @param The Occultist extension to register.
752
+ */
753
+ registerExtension(extension: Extension): void {
754
+ let staticAlias: string;
755
+ if (
756
+ typeof extension.getAsset === 'function' &&
757
+ Array.isArray(extension.staticAliases)
758
+ ) {
759
+ for (let i = 0; i < extension.staticAliases.length; i++) {
760
+ staticAlias = extension.staticAliases[i];
761
+
762
+ if (this.#staticExtensions.has(staticAlias)) {
763
+ throw new Error(`Static alias '${staticAlias}' already used by other extension`);
764
+ }
765
+
766
+ this.#staticExtensions.set(staticAlias, extension as StaticExtension);
767
+ }
768
+ }
769
+
770
+ this.#extensions.push(extension);
771
+ }
772
+
773
+ /**
774
+ * Must be called after all Occultist extensions have been registered.
775
+ * When some of the extensions have async setup tasks.
776
+ */
777
+ async setupExtensions(): Promise<void> {
778
+ const setupStreams: ReadableStream[] = [];
779
+
780
+ for (let i = 0; i < this.#extensions.length; i++) {
781
+ if (typeof this.#extensions[i].setup === 'function') {
782
+ setupStreams.push(this.#extensions[i].setup());
783
+ }
784
+ }
785
+
786
+ for (let i = 0; i < setupStreams.length; i++) {
787
+ for await (const message of setupStreams[i]) {
788
+ console.log(message);
789
+ }
790
+ }
791
+ }
792
+
734
793
  addEventListener(type: RegistryEvents, callback: EventListener) {
735
794
  this.#eventTarget.addEventListener(type, callback);
736
795
  };
package/lib/types.ts CHANGED
@@ -1,3 +1,44 @@
1
+
2
+ export interface StaticAsset {
3
+ alias: string;
4
+ contentType: string;
5
+ url: string;
6
+ integrity?: string;
7
+ };
8
+
9
+ export interface StaticContext {
10
+ link(alias: string, as: string): string;
11
+ }
12
+
13
+ export interface Extension {
14
+ name: string;
15
+ setup?(): ReadableStream;
16
+ getAsset?(): StaticAsset | undefined;
17
+ staticAliases?: string[];
18
+ };
19
+
20
+ export interface StaticExtension {
21
+
22
+ /**
23
+ * The name of the static extension.
24
+ */
25
+ name: string;
26
+
27
+ /**
28
+ * Root level aliases the extension uses to identify
29
+ * assets it manages.
30
+ */
31
+ staticAliases: string[];
32
+
33
+ /**
34
+ * Retrieves a static assets from the extension.
35
+ *
36
+ * @param assetAlias The alias for the asset.
37
+ */
38
+ getAsset(assetAlias: string): StaticAsset | undefined;
39
+
40
+ }
41
+
1
42
  export type ProblemDetailsParam = {
2
43
  name: string;
3
44
  reason: string;
package/package.json CHANGED
@@ -35,7 +35,7 @@
35
35
  "jsonld": "^9.0.0",
36
36
  "typescript": "^5.9.3"
37
37
  },
38
- "version": "0.0.8",
38
+ "version": "0.0.10",
39
39
  "scripts": {
40
40
  "build": "tsc -p tsconfig.build.json",
41
41
  "test": "node --test"