spooder 4.1.1 → 4.2.1

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.
package/README.md CHANGED
@@ -430,9 +430,9 @@ In addition to the information provided by the developer, `spooder` also include
430
430
  - [`ErrorWithMetadata(message: string, metadata: object)`](#api-error-handling-error-with-metadata)
431
431
  - [`caution(err_message_or_obj: string | object, ...err: object[]): Promise<void>`](#api-error-handling-caution)
432
432
  - [`panic(err_message_or_obj: string | object, ...err: object[]): Promise<void>`](#api-error-handling-panic)
433
+ - [`safe(fn: Callable): Promise<void>`](#api-error-handling-safe)
433
434
  - [API > Content](#api-content)
434
- - [`template_sub(template: string, replacements: Record<string, string>): string`](#api-content-template-sub)
435
- - [`template_sub_file(template_file: string, replacements: Record<string, string>): Promise<string>`](#api-content-template-sub-file)
435
+ - [`parse_template(template: string, replacements: Record<string, string>): string`](#api-content-parse-template)
436
436
  - [`generate_hash_subs(length: number, prefix: string): Promise<Record<string, string>>`](#api-content-generate-hash-subs)
437
437
  - [`apply_range(file: BunFile, request: Request): HandlerReturnType`](#api-content-apply-range)
438
438
  - [API > State Management](#api-state-management)
@@ -873,11 +873,35 @@ try {
873
873
  }
874
874
  ```
875
875
 
876
+ <a id="api-error-handling-safe"></a>
877
+ ### 🔧 `safe(fn: Callable): Promise<void>`
878
+
879
+ `safe()` is a utility function that wraps a "callable" and calls `caution()` if it throws an error.
880
+
881
+ > ![NOTE]
882
+ > This utility is primarily intended to be used to reduce boilerplate for fire-and-forget functions that you want to be notified about if they fail.
883
+
884
+ ```ts
885
+ safe(async (() => {
886
+ // This code will run async and any errors will invoke caution().
887
+ });
888
+ ```
889
+
890
+ `safe()` supports both async and sync callables, as well as Promise objects. `safe()` can also used with `await`.
891
+
892
+ ```ts
893
+ await safe(() => {
894
+ return new Promise((resolve, reject) => {
895
+ // Do stuff.
896
+ });
897
+ });
898
+ ```
899
+
876
900
  <a id="api-content"></a>
877
901
  ## API > Content
878
902
 
879
- <a id="api-content-template-sub"></a>
880
- ### 🔧 `template_sub(template: string, replacements: Record<string, string>): string`
903
+ <a id="api-content-parse-template"></a>
904
+ ### 🔧 `parse_template(template: string, replacements: Record<string, string>): string`
881
905
 
882
906
  Replace placeholders in a template string with values from a replacement object.
883
907
 
@@ -888,12 +912,12 @@ Replace placeholders in a template string with values from a replacement object.
888
912
  const template = `
889
913
  <html>
890
914
  <head>
891
- <title>{title}</title>
915
+ <title>{$title}</title>
892
916
  </head>
893
917
  <body>
894
- <h1>{title}</h1>
895
- <p>{content}</p>
896
- <p>{ignored}</p>
918
+ <h1>{$title}</h1>
919
+ <p>{$content}</p>
920
+ <p>{$ignored}</p>
897
921
  </body>
898
922
  </html>
899
923
  `;
@@ -903,7 +927,7 @@ const replacements = {
903
927
  content: 'This is a test.'
904
928
  };
905
929
 
906
- const html = template_sub(template, replacements);
930
+ const html = parse_template(template, replacements);
907
931
  ```
908
932
 
909
933
  ```html
@@ -914,28 +938,40 @@ const html = template_sub(template, replacements);
914
938
  <body>
915
939
  <h1>Hello, world!</h1>
916
940
  <p>This is a test.</p>
917
- <p>{ignored}</p>
941
+ <p>{$ignored}</p>
918
942
  </body>
919
943
  </html>
920
944
  ```
921
945
 
922
- <a id="api-content-template-sub-file"></a>
923
- ### 🔧 `template_sub_file(template_file: string, replacements: Record<string, string>): Promise<string>`
946
+ `parse_template` supports looping arrays with the following syntax.
924
947
 
925
- Replace placeholders in a template file with values from a replacement object.
948
+ ```html
949
+ {$for:foo}My colour is %s{/for}
950
+ ```
951
+ ```ts
952
+ const template = `
953
+ <ul>
954
+ {$for:foo}<li>%s</li>{/for}
955
+ </ul>
956
+ `;
926
957
 
927
- > [!NOTE]
928
- > This function is a convenience wrapper around `template_sub` and `Bun.file().text()` to reduce boilerplate. See `template_sub` for more information.
958
+ const replacements = {
959
+ foo: ['red', 'green', 'blue']
960
+ };
929
961
 
930
- ```ts
931
- const html = await template_sub_file('./template.html', replacements);
962
+ const html = parse_template(template, replacements);
963
+ ```
932
964
 
933
- // Is equivalent to:
934
- const file = Bun.file('./template.html');
935
- const file_contents = await file.text();
936
- const html = await template_sub(file_contents, replacements);
965
+ ```html
966
+ <ul>
967
+ <li>red</li>
968
+ <li>green</li>
969
+ <li>blue</li>
970
+ </ul>
937
971
  ```
938
972
 
973
+ > [!WARNING]
974
+ > Nested loops are not supported.
939
975
 
940
976
  <a id="api-content-generate-hash-subs"></a>
941
977
  ### 🔧 `generate_hash_subs(prefix: string): Promise<Record<string, string>>`
@@ -951,7 +987,7 @@ let hash_sub_table = {};
951
987
  generate_hash_subs().then(subs => hash_sub_table = subs).catch(caution);
952
988
 
953
989
  server.route('/test', (req, url) => {
954
- return template_sub('Hello world {hash=docs/project-logo.png}', hash_sub_table);
990
+ return parse_template('Hello world {hash=docs/project-logo.png}', hash_sub_table);
955
991
  });
956
992
  ```
957
993
 
@@ -978,7 +1014,7 @@ Use a different prefix other than `hash=` by passing it as the first parameter.
978
1014
  generate_hash_subs(7, '#').then(subs => hash_sub_table = subs).catch(caution);
979
1015
 
980
1016
  server.route('/test', (req, url) => {
981
- return template_sub('Hello world {#docs/project-logo.png}', hash_sub_table);
1017
+ return parse_template('Hello world {#docs/project-logo.png}', hash_sub_table);
982
1018
  });
983
1019
  ```
984
1020
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "spooder",
3
3
  "type": "module",
4
- "version": "4.1.1",
4
+ "version": "4.2.1",
5
5
  "exports": {
6
6
  ".": {
7
7
  "bun": "./src/api.ts",
package/src/api.d.ts CHANGED
@@ -15,8 +15,10 @@ export declare class ErrorWithMetadata extends Error {
15
15
  }
16
16
  export declare function panic(err_message_or_obj: string | object, ...err: object[]): Promise<void>;
17
17
  export declare function caution(err_message_or_obj: string | object, ...err: object[]): Promise<void>;
18
- export declare function template_sub_file(template_file: string, replacements: Record<string, string>): Promise<string>;
19
- export declare function template_sub(template: string, replacements: Record<string, string>): string;
18
+ type CallableFunction = (...args: any[]) => any;
19
+ type Callable = Promise<any> | CallableFunction;
20
+ export declare function safe(target_fn: Callable): Promise<void>;
21
+ export declare function parse_template(template: string, replacements: Record<string, string | Array<string>>): string;
20
22
  export declare function generate_hash_subs(length?: number, prefix?: string): Promise<Record<string, string>>;
21
23
  type CookieOptions = {
22
24
  same_site?: 'Strict' | 'Lax' | 'None';
package/src/api.ts CHANGED
@@ -89,14 +89,21 @@ export async function caution(err_message_or_obj: string | object, ...err: objec
89
89
  await handle_error('caution: ', err_message_or_obj, ...err);
90
90
  }
91
91
 
92
- export async function template_sub_file(template_file: string, replacements: Record<string, string>): Promise<string> {
93
- const file = Bun.file(template_file);
94
- const file_contents = await file.text();
95
-
96
- return template_sub(file_contents, replacements);
92
+ type CallableFunction = (...args: any[]) => any;
93
+ type Callable = Promise<any> | CallableFunction;
94
+
95
+ export async function safe(target_fn: Callable) {
96
+ try {
97
+ if (target_fn instanceof Promise)
98
+ await target_fn;
99
+ else
100
+ await target_fn();
101
+ } catch (e) {
102
+ caution(e as Error);
103
+ }
97
104
  }
98
105
 
99
- export function template_sub(template: string, replacements: Record<string, string>): string {
106
+ export function parse_template(template: string, replacements: Record<string, string | Array<string>>): string {
100
107
  let result = '';
101
108
  let buffer = '';
102
109
  let buffer_active = false;
@@ -105,14 +112,41 @@ export function template_sub(template: string, replacements: Record<string, stri
105
112
  for (let i = 0; i < template_length; i++) {
106
113
  const char = template[i];
107
114
 
108
- if (char === '{') {
115
+ if (char === '{' && template[i + 1] === '$') {
116
+ i++;
109
117
  buffer_active = true;
110
118
  buffer = '';
111
- } else if (char === '}') {
119
+ } else if (char === '}' && buffer_active) {
112
120
  buffer_active = false;
113
121
 
114
- result += replacements[buffer] ?? '{' + buffer + '}';
122
+ if (buffer.startsWith('for:')) {
123
+ const loop_key = buffer.substring(4);
124
+
125
+ const loop_entries = replacements[loop_key];
126
+ if (loop_entries !== undefined) {
127
+ const loop_content_start_index = i + 1;
128
+ const loop_close_index = template.indexOf('{/for}', loop_content_start_index);
129
+ const loop_content = template.substring(loop_content_start_index, loop_close_index);
130
+
131
+ // More performat than replaceAll on larger arrays (and equal on tiny arrays).
132
+ const content_parts = loop_content.split('%s');
133
+ const indicies = [] as Array<number>;
134
+
135
+ for (let j = 0; j < content_parts.length; j++)
136
+ if (content_parts[j] === '%s')
137
+ indicies.push(j);
115
138
 
139
+ for (const loop_entry of loop_entries)
140
+ for (const index of indicies)
141
+ content_parts[index] = loop_entry;
142
+
143
+ i += loop_content.length + 6;
144
+ } else {
145
+ result += '{$' + buffer + '}';
146
+ }
147
+ } else {
148
+ result += replacements[buffer] ?? '{$' + buffer + '}';
149
+ }
116
150
  buffer = '';
117
151
  } else if (buffer_active) {
118
152
  buffer += char;