structured-fw 1.7.5 → 1.7.8

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
@@ -206,6 +206,8 @@ Session allows you to store temporary data for the users of your web application
206
206
 
207
207
  Session data is tied to a visitor via sessionId, which is always available on `RequestContext`, which means you can interact with session data from routes and server side part of your components.
208
208
 
209
+ *Session data is stored in memory, which means it is lost when you restart the app. If you want to have persistent sessions that survive restart, you can use [structured-plugin-session-redis](https://www.npmjs.com/package/structured-plugin-session-redis).*
210
+
209
211
  **Configuration**\
210
212
  `StructuredConfig`.`session`:
211
213
  ```
@@ -590,6 +592,9 @@ Your lucky number is {{luckyNumber}}<br>
590
592
  ```
591
593
 
592
594
  That's it. `AnotherComponent` will receive the `luckyNumber` as a number, you can pass any type of data, string, number, boolean, object, array... it will be received by the child as the same type of data. However, *keep in mind the data gets serialized and de-serialized in the process, so if you pass an object to a child, it won't be a reference to the original object, rather a copy of it*.
595
+
596
+ Since version 1.7.6, additional data types can be passed to child components without them breaking due to serialization: Date, BigInt, RegExp, Map, Uint8Array. *Let me know if you need support for more types that JSON.stringify breaks.*
597
+
593
598
  \
594
599
  Let's see how we can use the passed data within `AnotherComponent`, create `/app/views/AnotherComponent/AnotherComponent.html`:
595
600
  ```
@@ -1,3 +1,4 @@
1
+ import { SerializableDate, SerializableRegExp, Serializable, ValueSerializable, SerializableBigInt, SerializableMap, SerializableUint8Array } from './types/component.types.js';
1
2
  import { LooseObject } from './types/general.types.js';
2
3
  import { PostedDataDecoded } from "./types/request.types.js";
3
4
  export declare function queryStringDecode(queryString: string, initialValue?: PostedDataDecoded, trimValues?: boolean): PostedDataDecoded;
@@ -10,13 +11,23 @@ export declare function isAsync(fn: Function): boolean;
10
11
  export declare function randomString(len: number, method?: 'alphanumeric' | 'numbers' | 'letters' | 'lettersUppercase' | 'lettersLowercase'): string;
11
12
  export declare function unique<T>(arr: Array<T>): Array<T>;
12
13
  export declare function stripTags(contentWithHTML: string, keepTags?: Array<string>): string;
14
+ export declare function toSerializableValue(value: any): ValueSerializable;
15
+ export declare function fromSerializableValue(data: SerializableDate): Date;
16
+ export declare function fromSerializableValue(data: SerializableRegExp): RegExp;
17
+ export declare function fromSerializableValue(data: SerializableMap): Map<any, any>;
18
+ export declare function fromSerializableValue(data: SerializableBigInt): BigInt;
19
+ export declare function fromSerializableValue(data: SerializableUint8Array): Uint8Array;
20
+ export declare function fromSerializableValue(data: Serializable): any;
21
+ export declare function serializableObject(data: LooseObject): LooseObject;
22
+ export declare function deserializeObject(data: LooseObject): LooseObject;
23
+ export declare function escapeAttributeValue(value: string): string;
24
+ export declare function unescapeAttributeValue(str: string): string;
13
25
  export declare function attributeValueToString(key: string, value: any): string;
14
26
  export declare function attributeValueFromString(attributeValue: string): string | {
15
27
  key: string;
16
28
  value: any;
17
29
  };
18
- export declare function attributeValueEscape(str: string): string;
19
- export declare function isObject(item: any): boolean;
30
+ export declare function isObject(value: any): boolean;
20
31
  export declare function equalDeep(a: LooseObject, b: LooseObject): boolean;
21
32
  export declare function mergeDeep(target: any, ...sources: Array<any>): LooseObject;
22
33
  export declare function stripBOM(str: string): string;
@@ -226,30 +226,128 @@ export function stripTags(contentWithHTML, keepTags = []) {
226
226
  return sub.replaceAll('<', '&lt;').replaceAll('>', '&gt;');
227
227
  });
228
228
  }
229
- function base64ToBytes(base64) {
230
- const binString = atob(base64);
231
- return Uint8Array.from(binString, (m) => m.codePointAt(0));
229
+ export function toSerializableValue(value) {
230
+ if (value instanceof Date) {
231
+ return {
232
+ __sv: 1,
233
+ type: 'date',
234
+ value: value.toISOString(),
235
+ };
236
+ }
237
+ if (typeof value === 'bigint') {
238
+ return {
239
+ __sv: 1,
240
+ type: 'bigint',
241
+ value: value.toString(),
242
+ };
243
+ }
244
+ if (value instanceof RegExp) {
245
+ return {
246
+ __sv: 1,
247
+ type: 'regexp',
248
+ value: {
249
+ source: value.source,
250
+ flags: value.flags,
251
+ }
252
+ };
253
+ }
254
+ if (value instanceof Map) {
255
+ return {
256
+ __sv: 1,
257
+ type: 'map',
258
+ value: [...value.entries()],
259
+ };
260
+ }
261
+ if (value instanceof Uint8Array) {
262
+ return {
263
+ __sv: 1,
264
+ type: 'uint8array',
265
+ value: Array.from(value),
266
+ };
267
+ }
268
+ return {
269
+ __sv: 1,
270
+ value,
271
+ };
272
+ }
273
+ export function fromSerializableValue(data) {
274
+ if (!('type' in data)) {
275
+ return data.value;
276
+ }
277
+ if (data.type === 'date') {
278
+ return new Date(data.value);
279
+ }
280
+ if (data.type === 'regexp') {
281
+ return new RegExp(data.value.source, data.value.flags);
282
+ }
283
+ if (data.type === 'map') {
284
+ return new Map(data.value);
285
+ }
286
+ if (data.type === 'bigint') {
287
+ return BigInt(data.value);
288
+ }
289
+ if (data.type === 'uint8array') {
290
+ return new Uint8Array(data.value);
291
+ }
292
+ return undefined;
232
293
  }
233
- function bytesToBase64(bytes) {
234
- return btoa(bytes.reduce((prev, curr) => {
235
- return prev + String.fromCharCode(curr);
236
- }, ''));
294
+ export function serializableObject(data) {
295
+ const copy = {};
296
+ objectEach(data, (key, val) => {
297
+ if (isObject(val)) {
298
+ copy[key] = serializableObject(val);
299
+ }
300
+ else {
301
+ copy[key] = toSerializableValue(val);
302
+ }
303
+ });
304
+ return copy;
305
+ }
306
+ export function deserializeObject(data) {
307
+ const copy = {};
308
+ objectEach(data, (key, val) => {
309
+ if (typeof val === 'object' && val !== null && val !== undefined) {
310
+ if ('__sv' in val) {
311
+ copy[key] = fromSerializableValue(val);
312
+ }
313
+ else {
314
+ copy[key] = deserializeObject(val);
315
+ }
316
+ }
317
+ else {
318
+ copy[key] = fromSerializableValue(val);
319
+ }
320
+ });
321
+ return copy;
322
+ }
323
+ export function escapeAttributeValue(value) {
324
+ return value
325
+ .replace(/~/g, '~~')
326
+ .replace(/"/g, '~q');
327
+ }
328
+ export function unescapeAttributeValue(str) {
329
+ const tempToken = `--${randomString(10)}--`;
330
+ return str
331
+ .split('~~').join(tempToken)
332
+ .split('~q').join('"')
333
+ .split(tempToken).join('~');
237
334
  }
238
335
  export function attributeValueToString(key, value) {
239
- return 'base64:' + bytesToBase64(new TextEncoder().encode(JSON.stringify({ key, value })));
336
+ const data = serializableObject({ key, data: value });
337
+ return escapeAttributeValue(JSON.stringify(data));
240
338
  }
241
339
  export function attributeValueFromString(attributeValue) {
242
- if (attributeValue.indexOf('base64:') === 0) {
340
+ if (attributeValue.indexOf('{') === 0) {
243
341
  try {
244
- const decoded = new TextDecoder().decode(base64ToBytes(attributeValue.substring(7)));
245
- if (decoded.indexOf('{') !== 0) {
246
- return attributeValue;
247
- }
248
- const valObj = JSON.parse(decoded);
342
+ const decoded = unescapeAttributeValue(attributeValue);
343
+ const valObj = deserializeObject(JSON.parse(decoded));
249
344
  if (!('key' in valObj)) {
250
345
  return decoded;
251
346
  }
252
- return valObj;
347
+ return {
348
+ key: valObj.key,
349
+ value: valObj.data,
350
+ };
253
351
  }
254
352
  catch (e) {
255
353
  return attributeValue;
@@ -257,14 +355,11 @@ export function attributeValueFromString(attributeValue) {
257
355
  }
258
356
  return attributeValue;
259
357
  }
260
- export function attributeValueEscape(str) {
261
- return str.replaceAll('"', '&quot;');
262
- }
263
- export function isObject(item) {
264
- if (typeof window === 'undefined') {
265
- return (item && typeof item === 'object' && !Array.isArray(item)) && !Buffer.isBuffer(item);
266
- }
267
- return (item && typeof item === 'object' && !Array.isArray(item));
358
+ export function isObject(value) {
359
+ if (value === null || typeof value !== 'object')
360
+ return false;
361
+ const proto = Object.getPrototypeOf(value);
362
+ return proto === Object.prototype || proto === null;
268
363
  }
269
364
  export function equalDeep(a, b) {
270
365
  if (a === b) {
@@ -39,6 +39,43 @@ export type ComponentEvents = {
39
39
  componentCreated: Component;
40
40
  ready: undefined;
41
41
  };
42
+ export type Serializable = {
43
+ __sv: 1;
44
+ value: any;
45
+ };
46
+ export type SerializableDate = {
47
+ __sv: 1;
48
+ type: 'date';
49
+ value: string;
50
+ };
51
+ export type SerializableRegExp = {
52
+ __sv: 1;
53
+ type: 'regexp';
54
+ value: {
55
+ source: string;
56
+ flags: string;
57
+ };
58
+ };
59
+ export type SerializableMap = {
60
+ __sv: 1;
61
+ type: 'map';
62
+ value: Array<[any, any]>;
63
+ };
64
+ export type SerializableBigInt = {
65
+ __sv: 1;
66
+ type: 'bigint';
67
+ value: string;
68
+ };
69
+ export type SerializableUint8Array = {
70
+ __sv: 1;
71
+ type: 'uint8array';
72
+ value: Array<number>;
73
+ };
74
+ export type ValueSerializable = Serializable | SerializableDate | SerializableRegExp | SerializableMap | SerializableBigInt | SerializableUint8Array;
75
+ export type AttributeEncodedObject = {
76
+ key: string;
77
+ data: ValueSerializable;
78
+ };
42
79
  export type ClientComponentTransition = {
43
80
  fade: false | number;
44
81
  slide: false | number;
package/package.json CHANGED
@@ -19,7 +19,7 @@
19
19
  "license": "MIT",
20
20
  "type": "module",
21
21
  "main": "build/index",
22
- "version": "1.7.5",
22
+ "version": "1.7.8",
23
23
  "scripts": {
24
24
  "develop": "tsc --watch",
25
25
  "startDev": "cd build && nodemon --watch '../app/**/*' --watch '../build/**/*' -e js,html,hbs,css index.js",