@shopify/shopify-app-session-storage-redis 1.0.1 → 1.1.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.
@@ -3,15 +3,20 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var shopifyApi = require('@shopify/shopify-api');
6
+ var shopifyAppSessionStorage = require('@shopify/shopify-app-session-storage');
7
+
8
+ const migrationList = [new shopifyAppSessionStorage.MigrationOperation(
9
+ // This migration name cannot be changed as it has already been released
10
+ 'migrateToVersion1_0_1', migrateAddShopKeyToTrackSessionsByShop)];
6
11
 
7
12
  // need to add shop keys with list of associated session keys to support
8
13
  // the new findSessionsByShop in v2.x.x
9
- async function migrateToVersion1_0_1(client, sessionKeyPrefix, fullKey) {
14
+ async function migrateAddShopKeyToTrackSessionsByShop(connection) {
10
15
  const shopsAndSessions = {};
11
- const keys = await client.keys('*');
16
+ const keys = await connection.keys('*');
12
17
  for (const key of keys) {
13
- if (key.startsWith(sessionKeyPrefix)) {
14
- const session = shopifyApi.Session.fromPropertyArray(JSON.parse(await client.get(key)));
18
+ if (key.startsWith(connection.sessionStorageIdentifier)) {
19
+ const session = shopifyApi.Session.fromPropertyArray(JSON.parse(await connection.get(key, false)));
15
20
  if (!shopsAndSessions[session.shop]) {
16
21
  shopsAndSessions[session.shop] = [];
17
22
  }
@@ -20,8 +25,9 @@ async function migrateToVersion1_0_1(client, sessionKeyPrefix, fullKey) {
20
25
  }
21
26
  // eslint-disable-next-line guard-for-in
22
27
  for (const shop in shopsAndSessions) {
23
- await client.set(fullKey(shop), JSON.stringify(shopsAndSessions[shop]));
28
+ await connection.set(shop, JSON.stringify(shopsAndSessions[shop]));
24
29
  }
25
30
  }
26
31
 
27
- exports.migrateToVersion1_0_1 = migrateToVersion1_0_1;
32
+ exports.migrateAddShopKeyToTrackSessionsByShop = migrateAddShopKeyToTrackSessionsByShop;
33
+ exports.migrationList = migrationList;
@@ -0,0 +1,52 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var redis = require('redis');
6
+
7
+ class RedisConnection {
8
+ constructor(dbUrl, options, keyPrefix, onError) {
9
+ this.sessionStorageIdentifier = void 0;
10
+ this.client = void 0;
11
+ this.init(dbUrl, options);
12
+ this.sessionStorageIdentifier = keyPrefix;
13
+ if (onError) {
14
+ this.client.on('error', onError);
15
+ }
16
+ }
17
+ query(_query, _params) {
18
+ throw new Error('Method not implemented. Use get(string, boolean) instead');
19
+ }
20
+ async connect() {
21
+ return this.client.connect();
22
+ }
23
+ async disconnect() {
24
+ await this.client.quit();
25
+ }
26
+ async keys(name) {
27
+ return this.client.keys(name);
28
+ }
29
+ async set(baseKey, value, addKeyPrefix = true) {
30
+ await this.client.set(this.buildKey(baseKey, addKeyPrefix), value);
31
+ }
32
+ async del(baseKey, addKeyPrefix = true) {
33
+ return this.client.del(this.buildKey(baseKey, addKeyPrefix));
34
+ }
35
+ async get(baseKey, addKeyPrefix = true) {
36
+ return this.client.get(this.buildKey(baseKey, addKeyPrefix));
37
+ }
38
+ generateFullKey(name) {
39
+ return `${this.sessionStorageIdentifier}_${name}`;
40
+ }
41
+ buildKey(baseKey, addKeyPrefix) {
42
+ return addKeyPrefix ? this.generateFullKey(baseKey) : baseKey;
43
+ }
44
+ init(dbUrl, options) {
45
+ this.client = redis.createClient({
46
+ ...options,
47
+ url: dbUrl
48
+ });
49
+ }
50
+ }
51
+
52
+ exports.RedisConnection = RedisConnection;
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var shopifyAppSessionStorage = require('@shopify/shopify-app-session-storage');
6
+
7
+ class RedisSessionStorageMigrator extends shopifyAppSessionStorage.AbstractMigrationEngine {
8
+ constructor(dbConnection, opts = {}) {
9
+ // The name has already been decided whith the last migration
10
+ opts.migrationDBIdentifier = 'migrations';
11
+ super(dbConnection, opts);
12
+ }
13
+ async initMigrationPersistence() {
14
+ // nothing to do here
15
+ return Promise.resolve();
16
+ }
17
+ async hasMigrationBeenApplied(migrationName) {
18
+ var _migrations$get;
19
+ const migrations = await this.getMigrationRecords();
20
+ const found = (_migrations$get = migrations.get(migrationName)) !== null && _migrations$get !== void 0 ? _migrations$get : false;
21
+ return Promise.resolve(found);
22
+ }
23
+ async saveAppliedMigration(migrationName) {
24
+ const migrations = await this.getMigrationRecords();
25
+ migrations.set(migrationName, true);
26
+ this.connection.set(this.options.migrationDBIdentifier, JSON.stringify(Object.fromEntries(migrations)));
27
+ return Promise.resolve();
28
+ }
29
+ async getMigrationRecords() {
30
+ const migrationsRecord = await this.connection.get(this.options.migrationDBIdentifier);
31
+ let migrations = new Map();
32
+ if (migrationsRecord) {
33
+ migrations = new Map(Object.entries(JSON.parse(migrationsRecord)));
34
+ }
35
+ return Promise.resolve(migrations);
36
+ }
37
+ }
38
+
39
+ exports.RedisSessionStorageMigrator = RedisSessionStorageMigrator;
@@ -2,40 +2,44 @@
2
2
 
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
- var redis = require('redis');
6
5
  var shopifyApi = require('@shopify/shopify-api');
7
6
  var migrations = require('./migrations.js');
7
+ var redisConnection = require('./redis-connection.js');
8
+ var redisMigrator = require('./redis-migrator.js');
8
9
 
9
10
  const defaultRedisSessionStorageOptions = {
10
- sessionKeyPrefix: 'shopify_sessions'
11
+ sessionKeyPrefix: 'shopify_sessions',
12
+ migratorOptions: {
13
+ migrationDBIdentifier: 'migrations',
14
+ migrations: migrations.migrationList
15
+ }
11
16
  };
12
17
  class RedisSessionStorage {
13
18
  static withCredentials(host, db, username, password, opts) {
14
19
  return new RedisSessionStorage(new URL(`redis://${encodeURIComponent(username)}:${encodeURIComponent(password)}@${host}/${db}`), opts);
15
20
  }
16
21
  constructor(dbUrl, opts = {}) {
17
- this.dbUrl = dbUrl;
18
22
  this.ready = void 0;
23
+ this.internalInit = void 0;
19
24
  this.options = void 0;
20
25
  this.client = void 0;
21
- if (typeof this.dbUrl === 'string') {
22
- this.dbUrl = new URL(this.dbUrl);
23
- }
26
+ this.migrator = void 0;
24
27
  this.options = {
25
28
  ...defaultRedisSessionStorageOptions,
26
29
  ...opts
27
30
  };
28
- this.ready = this.init();
31
+ this.internalInit = this.init(dbUrl.toString());
32
+ this.ready = this.initMigrator(this.options.migratorOptions);
29
33
  }
30
34
  async storeSession(session) {
31
35
  await this.ready;
32
- await this.client.set(this.fullKey(session.id), JSON.stringify(session.toPropertyArray()));
36
+ await this.client.set(session.id, JSON.stringify(session.toPropertyArray()));
33
37
  await this.addKeyToShopList(session);
34
38
  return true;
35
39
  }
36
40
  async loadSession(id) {
37
41
  await this.ready;
38
- let rawResult = await this.client.get(this.fullKey(id));
42
+ let rawResult = await this.client.get(id);
39
43
  if (!rawResult) return undefined;
40
44
  rawResult = JSON.parse(rawResult);
41
45
  return shopifyApi.Session.fromPropertyArray(rawResult);
@@ -45,7 +49,7 @@ class RedisSessionStorage {
45
49
  const session = await this.loadSession(id);
46
50
  if (session) {
47
51
  await this.removeKeyFromShopList(session.shop, id);
48
- await this.client.del(this.fullKey(id));
52
+ await this.client.del(id);
49
53
  }
50
54
  return true;
51
55
  }
@@ -56,12 +60,12 @@ class RedisSessionStorage {
56
60
  }
57
61
  async findSessionsByShop(shop) {
58
62
  await this.ready;
59
- const idKeysArrayString = await this.client.get(this.fullKey(shop));
63
+ const idKeysArrayString = await this.client.get(shop);
60
64
  if (!idKeysArrayString) return [];
61
65
  const idKeysArray = JSON.parse(idKeysArrayString);
62
66
  const results = [];
63
67
  for (const idKey of idKeysArray) {
64
- const rawResult = await this.client.get(idKey);
68
+ const rawResult = await this.client.get(idKey, false);
65
69
  if (!rawResult) continue;
66
70
  const session = shopifyApi.Session.fromPropertyArray(JSON.parse(rawResult));
67
71
  results.push(session);
@@ -69,14 +73,11 @@ class RedisSessionStorage {
69
73
  return results;
70
74
  }
71
75
  async disconnect() {
72
- await this.client.quit();
73
- }
74
- fullKey(name) {
75
- return `${this.options.sessionKeyPrefix}_${name}`;
76
+ await this.client.disconnect();
76
77
  }
77
78
  async addKeyToShopList(session) {
78
- const shopKey = this.fullKey(session.shop);
79
- const idKey = this.fullKey(session.id);
79
+ const shopKey = session.shop;
80
+ const idKey = this.client.generateFullKey(session.id);
80
81
  const idKeysArrayString = await this.client.get(shopKey);
81
82
  if (idKeysArrayString) {
82
83
  const idKeysArray = JSON.parse(idKeysArrayString);
@@ -89,8 +90,8 @@ class RedisSessionStorage {
89
90
  }
90
91
  }
91
92
  async removeKeyFromShopList(shop, id) {
92
- const shopKey = this.fullKey(shop);
93
- const idKey = this.fullKey(id);
93
+ const shopKey = shop;
94
+ const idKey = this.client.generateFullKey(id);
94
95
  const idKeysArrayString = await this.client.get(shopKey);
95
96
  if (idKeysArrayString) {
96
97
  const idKeysArray = JSON.parse(idKeysArrayString);
@@ -101,28 +102,19 @@ class RedisSessionStorage {
101
102
  }
102
103
  }
103
104
  }
104
- async init() {
105
- this.client = redis.createClient({
106
- ...this.options,
107
- url: this.dbUrl.toString()
108
- });
109
- if (this.options.onError) {
110
- this.client.on('error', this.options.onError);
111
- }
105
+ async init(dbUrl) {
106
+ this.client = new redisConnection.RedisConnection(dbUrl, this.options, this.options.sessionKeyPrefix);
112
107
  await this.client.connect();
113
- await this.migrate();
114
108
  }
115
- async migrate() {
116
- const migrationsRecord = await this.client.get(this.fullKey('migrations'));
117
- let migrations$1 = {};
118
- if (migrationsRecord) {
119
- migrations$1 = JSON.parse(migrationsRecord);
120
- }
121
- if (!migrations$1.migrateToVersion1_0_1) {
122
- await migrations.migrateToVersion1_0_1(this.client, this.options.sessionKeyPrefix, this.fullKey.bind(this));
123
- migrations$1.migrateToVersion1_0_1 = true;
109
+ async initMigrator(migratorOptions) {
110
+ await this.internalInit;
111
+ if (migratorOptions === null) {
112
+ return Promise.resolve();
113
+ } else {
114
+ this.migrator = new redisMigrator.RedisSessionStorageMigrator(this.client, migratorOptions);
115
+ this.migrator.validateMigrationList(migrations.migrationList);
116
+ return this.migrator.applyMigrations();
124
117
  }
125
- await this.client.set(this.fullKey('migrations'), JSON.stringify(migrations$1));
126
118
  }
127
119
  }
128
120
 
@@ -1,5 +1,5 @@
1
- import { createClient } from 'redis';
2
- type RedisClient = ReturnType<typeof createClient>;
3
- export declare function migrateToVersion1_0_1(client: RedisClient, sessionKeyPrefix: string, fullKey: (name: string) => string): Promise<void>;
4
- export {};
1
+ import { MigrationOperation } from '@shopify/shopify-app-session-storage';
2
+ import { RedisConnection } from './redis-connection';
3
+ export declare const migrationList: MigrationOperation[];
4
+ export declare function migrateAddShopKeyToTrackSessionsByShop(connection: RedisConnection): Promise<void>;
5
5
  //# sourceMappingURL=migrations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/migrations.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,YAAY,EAAC,MAAM,OAAO,CAAC;AAGnC,KAAK,WAAW,GAAG,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC;AAInD,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,WAAW,EACnB,gBAAgB,EAAE,MAAM,EACxB,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,iBAmBlC"}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../../src/migrations.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,kBAAkB,EAAC,MAAM,sCAAsC,CAAC;AAExE,OAAO,EAAC,eAAe,EAAC,MAAM,oBAAoB,CAAC;AAEnD,eAAO,MAAM,aAAa,sBAMzB,CAAC;AAIF,wBAAsB,sCAAsC,CAC1D,UAAU,EAAE,eAAe,GAC1B,OAAO,CAAC,IAAI,CAAC,CAkBf"}
@@ -1,11 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.migrateToVersion1_0_1 = void 0;
3
+ exports.migrateAddShopKeyToTrackSessionsByShop = exports.migrationList = void 0;
4
4
  var tslib_1 = require("tslib");
5
5
  var shopify_api_1 = require("@shopify/shopify-api");
6
+ var shopify_app_session_storage_1 = require("@shopify/shopify-app-session-storage");
7
+ exports.migrationList = [
8
+ new shopify_app_session_storage_1.MigrationOperation(
9
+ // This migration name cannot be changed as it has already been released
10
+ 'migrateToVersion1_0_1', migrateAddShopKeyToTrackSessionsByShop),
11
+ ];
6
12
  // need to add shop keys with list of associated session keys to support
7
13
  // the new findSessionsByShop in v2.x.x
8
- function migrateToVersion1_0_1(client, sessionKeyPrefix, fullKey) {
14
+ function migrateAddShopKeyToTrackSessionsByShop(connection) {
9
15
  return tslib_1.__awaiter(this, void 0, void 0, function () {
10
16
  var shopsAndSessions, keys, keys_1, keys_1_1, key, session, _a, _b, _c, _d, e_1_1, _e, _f, _g, _i, shop;
11
17
  var e_1, _h;
@@ -13,7 +19,7 @@ function migrateToVersion1_0_1(client, sessionKeyPrefix, fullKey) {
13
19
  switch (_j.label) {
14
20
  case 0:
15
21
  shopsAndSessions = {};
16
- return [4 /*yield*/, client.keys('*')];
22
+ return [4 /*yield*/, connection.keys('*')];
17
23
  case 1:
18
24
  keys = _j.sent();
19
25
  _j.label = 2;
@@ -24,10 +30,10 @@ function migrateToVersion1_0_1(client, sessionKeyPrefix, fullKey) {
24
30
  case 3:
25
31
  if (!!keys_1_1.done) return [3 /*break*/, 6];
26
32
  key = keys_1_1.value;
27
- if (!key.startsWith(sessionKeyPrefix)) return [3 /*break*/, 5];
33
+ if (!key.startsWith(connection.sessionStorageIdentifier)) return [3 /*break*/, 5];
28
34
  _b = (_a = shopify_api_1.Session).fromPropertyArray;
29
35
  _d = (_c = JSON).parse;
30
- return [4 /*yield*/, client.get(key)];
36
+ return [4 /*yield*/, connection.get(key, false)];
31
37
  case 4:
32
38
  session = _b.apply(_a, [_d.apply(_c, [(_j.sent())])]);
33
39
  if (!shopsAndSessions[session.shop]) {
@@ -61,7 +67,7 @@ function migrateToVersion1_0_1(client, sessionKeyPrefix, fullKey) {
61
67
  _g = _f[_i];
62
68
  if (!(_g in _e)) return [3 /*break*/, 12];
63
69
  shop = _g;
64
- return [4 /*yield*/, client.set(fullKey(shop), JSON.stringify(shopsAndSessions[shop]))];
70
+ return [4 /*yield*/, connection.set(shop, JSON.stringify(shopsAndSessions[shop]))];
65
71
  case 11:
66
72
  _j.sent();
67
73
  _j.label = 12;
@@ -73,5 +79,5 @@ function migrateToVersion1_0_1(client, sessionKeyPrefix, fullKey) {
73
79
  });
74
80
  });
75
81
  }
76
- exports.migrateToVersion1_0_1 = migrateToVersion1_0_1;
82
+ exports.migrateAddShopKeyToTrackSessionsByShop = migrateAddShopKeyToTrackSessionsByShop;
77
83
  //# sourceMappingURL=migrations.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/migrations.ts"],"names":[],"mappings":";;;;AACA,oDAA6C;AAI7C,wEAAwE;AACxE,uCAAuC;AACvC,SAAsB,qBAAqB,CACzC,MAAmB,EACnB,gBAAwB,EACxB,OAAiC;;;;;;;oBAE3B,gBAAgB,GAA8B,EAAE,CAAC;oBAC1C,qBAAM,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAA;;oBAA7B,IAAI,GAAG,SAAsB;;;;oBACjB,SAAA,iBAAA,IAAI,CAAA;;;;oBAAX,GAAG;yBACR,GAAG,CAAC,UAAU,CAAC,gBAAgB,CAAC,EAAhC,wBAAgC;oBAClB,KAAA,CAAA,KAAA,qBAAO,CAAA,CAAC,iBAAiB,CAAA;oBACvC,KAAA,CAAA,KAAA,IAAI,CAAA,CAAC,KAAK,CAAA;oBAAE,qBAAM,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,EAAA;;oBAD7B,OAAO,GAAG,cACd,cAAW,CAAC,SAAqB,CAAW,EAAC,EAC9C;oBACD,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;wBACnC,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;qBACrC;oBACD,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;;;;;;;;;;;;;;;;;yBAI1B,gBAAgB;;;;;;;;;;;oBACjC,qBAAM,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAA;;oBAAvE,SAAuE,CAAC;;;;;;;;;CAE3E;AAtBD,sDAsBC"}
1
+ {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../../src/migrations.ts"],"names":[],"mappings":";;;;AAAA,oDAA6C;AAC7C,oFAAwE;AAI3D,QAAA,aAAa,GAAG;IAC3B,IAAI,gDAAkB;IACpB,wEAAwE;IACxE,uBAAuB,EACvB,sCAAsC,CACvC;CACF,CAAC;AAEF,wEAAwE;AACxE,uCAAuC;AACvC,SAAsB,sCAAsC,CAC1D,UAA2B;;;;;;;oBAErB,gBAAgB,GAA8B,EAAE,CAAC;oBAC1C,qBAAM,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,EAAA;;oBAAjC,IAAI,GAAG,SAA0B;;;;oBACrB,SAAA,iBAAA,IAAI,CAAA;;;;oBAAX,GAAG;yBACR,GAAG,CAAC,UAAU,CAAC,UAAU,CAAC,wBAAwB,CAAC,EAAnD,wBAAmD;oBACrC,KAAA,CAAA,KAAA,qBAAO,CAAA,CAAC,iBAAiB,CAAA;oBACvC,KAAA,CAAA,KAAA,IAAI,CAAA,CAAC,KAAK,CAAA;oBAAE,qBAAM,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,EAAA;;oBADxC,OAAO,GAAG,cACd,cAAW,CAAC,SAAgC,CAAW,EAAC,EACzD;oBACD,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;wBACnC,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;qBACrC;oBACD,gBAAgB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;;;;;;;;;;;;;;;;;yBAI1B,gBAAgB;;;;;;;;;;;oBACjC,qBAAM,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,gBAAgB,CAAC,IAAI,CAAC,CAAC,CAAC,EAAA;;oBAAlE,SAAkE,CAAC;;;;;;;;;CAEtE;AApBD,wFAoBC"}
@@ -0,0 +1,18 @@
1
+ import { RedisClientOptions } from 'redis';
2
+ import { DBConnection } from '@shopify/shopify-app-session-storage';
3
+ export declare class RedisConnection implements DBConnection {
4
+ sessionStorageIdentifier: string;
5
+ private client;
6
+ constructor(dbUrl: string, options: RedisClientOptions, keyPrefix: string, onError?: (...args: any[]) => void);
7
+ query(_query: string, _params: any[]): Promise<any[]>;
8
+ connect(): Promise<void>;
9
+ disconnect(): Promise<void>;
10
+ keys(name: string): Promise<any>;
11
+ set(baseKey: string, value: any, addKeyPrefix?: boolean): Promise<void>;
12
+ del(baseKey: string, addKeyPrefix?: boolean): Promise<any>;
13
+ get(baseKey: string, addKeyPrefix?: boolean): Promise<any>;
14
+ generateFullKey(name: string): string;
15
+ private buildKey;
16
+ private init;
17
+ }
18
+ //# sourceMappingURL=redis-connection.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-connection.d.ts","sourceRoot":"","sources":["../../src/redis-connection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,kBAAkB,EAAC,MAAM,OAAO,CAAC;AACvD,OAAO,EAAC,YAAY,EAAC,MAAM,sCAAsC,CAAC;AAIlE,qBAAa,eAAgB,YAAW,YAAY;IAClD,wBAAwB,EAAE,MAAM,CAAC;IACjC,OAAO,CAAC,MAAM,CAAc;gBAG1B,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,kBAAkB,EAC3B,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI;IAUpC,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAI/C,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAI3B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC;IAIhC,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,YAAY,UAAO;IAIpD,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,UAAO,GAAG,OAAO,CAAC,GAAG,CAAC;IAIvD,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,UAAO,GAAG,OAAO,CAAC,GAAG,CAAC;IAI7D,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM;IAIrC,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,IAAI;CAMb"}
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RedisConnection = void 0;
4
+ var tslib_1 = require("tslib");
5
+ var redis_1 = require("redis");
6
+ var RedisConnection = /** @class */ (function () {
7
+ function RedisConnection(dbUrl, options, keyPrefix, onError) {
8
+ this.init(dbUrl, options);
9
+ this.sessionStorageIdentifier = keyPrefix;
10
+ if (onError) {
11
+ this.client.on('error', onError);
12
+ }
13
+ }
14
+ RedisConnection.prototype.query = function (_query, _params) {
15
+ throw new Error('Method not implemented. Use get(string, boolean) instead');
16
+ };
17
+ RedisConnection.prototype.connect = function () {
18
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
19
+ return tslib_1.__generator(this, function (_a) {
20
+ return [2 /*return*/, this.client.connect()];
21
+ });
22
+ });
23
+ };
24
+ RedisConnection.prototype.disconnect = function () {
25
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
26
+ return tslib_1.__generator(this, function (_a) {
27
+ switch (_a.label) {
28
+ case 0: return [4 /*yield*/, this.client.quit()];
29
+ case 1:
30
+ _a.sent();
31
+ return [2 /*return*/];
32
+ }
33
+ });
34
+ });
35
+ };
36
+ RedisConnection.prototype.keys = function (name) {
37
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
38
+ return tslib_1.__generator(this, function (_a) {
39
+ return [2 /*return*/, this.client.keys(name)];
40
+ });
41
+ });
42
+ };
43
+ RedisConnection.prototype.set = function (baseKey, value, addKeyPrefix) {
44
+ if (addKeyPrefix === void 0) { addKeyPrefix = true; }
45
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
46
+ return tslib_1.__generator(this, function (_a) {
47
+ switch (_a.label) {
48
+ case 0: return [4 /*yield*/, this.client.set(this.buildKey(baseKey, addKeyPrefix), value)];
49
+ case 1:
50
+ _a.sent();
51
+ return [2 /*return*/];
52
+ }
53
+ });
54
+ });
55
+ };
56
+ RedisConnection.prototype.del = function (baseKey, addKeyPrefix) {
57
+ if (addKeyPrefix === void 0) { addKeyPrefix = true; }
58
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
59
+ return tslib_1.__generator(this, function (_a) {
60
+ return [2 /*return*/, this.client.del(this.buildKey(baseKey, addKeyPrefix))];
61
+ });
62
+ });
63
+ };
64
+ RedisConnection.prototype.get = function (baseKey, addKeyPrefix) {
65
+ if (addKeyPrefix === void 0) { addKeyPrefix = true; }
66
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
67
+ return tslib_1.__generator(this, function (_a) {
68
+ return [2 /*return*/, this.client.get(this.buildKey(baseKey, addKeyPrefix))];
69
+ });
70
+ });
71
+ };
72
+ RedisConnection.prototype.generateFullKey = function (name) {
73
+ return "".concat(this.sessionStorageIdentifier, "_").concat(name);
74
+ };
75
+ RedisConnection.prototype.buildKey = function (baseKey, addKeyPrefix) {
76
+ return addKeyPrefix ? this.generateFullKey(baseKey) : baseKey;
77
+ };
78
+ RedisConnection.prototype.init = function (dbUrl, options) {
79
+ this.client = (0, redis_1.createClient)(tslib_1.__assign(tslib_1.__assign({}, options), { url: dbUrl }));
80
+ };
81
+ return RedisConnection;
82
+ }());
83
+ exports.RedisConnection = RedisConnection;
84
+ //# sourceMappingURL=redis-connection.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-connection.js","sourceRoot":"","sources":["../../src/redis-connection.ts"],"names":[],"mappings":";;;;AAAA,+BAAuD;AAKvD;IAIE,yBACE,KAAa,EACb,OAA2B,EAC3B,SAAiB,EACjB,OAAkC;QAElC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAC1B,IAAI,CAAC,wBAAwB,GAAG,SAAS,CAAC;QAE1C,IAAI,OAAO,EAAE;YACX,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;SAClC;IACH,CAAC;IAED,+BAAK,GAAL,UAAM,MAAc,EAAE,OAAc;QAClC,MAAM,IAAI,KAAK,CAAC,0DAA0D,CAAC,CAAC;IAC9E,CAAC;IAEK,iCAAO,GAAb;;;gBACE,sBAAO,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,EAAC;;;KAC9B;IAEK,oCAAU,GAAhB;;;;4BACE,qBAAM,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,EAAA;;wBAAxB,SAAwB,CAAC;;;;;KAC1B;IAEK,8BAAI,GAAV,UAAW,IAAY;;;gBACrB,sBAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAC;;;KAC/B;IAEK,6BAAG,GAAT,UAAU,OAAe,EAAE,KAAU,EAAE,YAAmB;QAAnB,6BAAA,EAAA,mBAAmB;;;;4BACxD,qBAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC,EAAE,KAAK,CAAC,EAAA;;wBAAlE,SAAkE,CAAC;;;;;KACpE;IAEK,6BAAG,GAAT,UAAU,OAAe,EAAE,YAAmB;QAAnB,6BAAA,EAAA,mBAAmB;;;gBAC5C,sBAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,EAAC;;;KAC9D;IAEK,6BAAG,GAAT,UAAU,OAAe,EAAE,YAAmB;QAAnB,6BAAA,EAAA,mBAAmB;;;gBAC5C,sBAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,EAAC;;;KAC9D;IAED,yCAAe,GAAf,UAAgB,IAAY;QAC1B,OAAO,UAAG,IAAI,CAAC,wBAAwB,cAAI,IAAI,CAAE,CAAC;IACpD,CAAC;IAEO,kCAAQ,GAAhB,UAAiB,OAAe,EAAE,YAAqB;QACrD,OAAO,YAAY,CAAC,CAAC,CAAC,IAAI,CAAC,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC;IAChE,CAAC;IAEO,8BAAI,GAAZ,UAAa,KAAa,EAAE,OAA2B;QACrD,IAAI,CAAC,MAAM,GAAG,IAAA,oBAAY,wCACrB,OAAO,KACV,GAAG,EAAE,KAAK,IACV,CAAC;IACL,CAAC;IACH,sBAAC;AAAD,CAAC,AA5DD,IA4DC;AA5DY,0CAAe"}
@@ -0,0 +1,10 @@
1
+ import { AbstractMigrationEngine, SessionStorageMigratorOptions } from '@shopify/shopify-app-session-storage';
2
+ import { RedisConnection } from './redis-connection';
3
+ export declare class RedisSessionStorageMigrator extends AbstractMigrationEngine<RedisConnection, SessionStorageMigratorOptions> {
4
+ constructor(dbConnection: RedisConnection, opts?: Partial<SessionStorageMigratorOptions>);
5
+ initMigrationPersistence(): Promise<void>;
6
+ hasMigrationBeenApplied(migrationName: string): Promise<boolean>;
7
+ saveAppliedMigration(migrationName: string): Promise<void>;
8
+ private getMigrationRecords;
9
+ }
10
+ //# sourceMappingURL=redis-migrator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-migrator.d.ts","sourceRoot":"","sources":["../../src/redis-migrator.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,uBAAuB,EACvB,6BAA6B,EAC9B,MAAM,sCAAsC,CAAC;AAE9C,OAAO,EAAC,eAAe,EAAC,MAAM,oBAAoB,CAAC;AAEnD,qBAAa,2BAA4B,SAAQ,uBAAuB,CACtE,eAAe,EACf,6BAA6B,CAC9B;gBAEG,YAAY,EAAE,eAAe,EAC7B,IAAI,GAAE,OAAO,CAAC,6BAA6B,CAAM;IAO7C,wBAAwB,IAAI,OAAO,CAAC,IAAI,CAAC;IAKzC,uBAAuB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAOhE,oBAAoB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAYlD,mBAAmB;CAWlC"}
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RedisSessionStorageMigrator = void 0;
4
+ var tslib_1 = require("tslib");
5
+ var shopify_app_session_storage_1 = require("@shopify/shopify-app-session-storage");
6
+ var RedisSessionStorageMigrator = /** @class */ (function (_super) {
7
+ tslib_1.__extends(RedisSessionStorageMigrator, _super);
8
+ function RedisSessionStorageMigrator(dbConnection, opts) {
9
+ if (opts === void 0) { opts = {}; }
10
+ // The name has already been decided whith the last migration
11
+ opts.migrationDBIdentifier = 'migrations';
12
+ return _super.call(this, dbConnection, opts) || this;
13
+ }
14
+ RedisSessionStorageMigrator.prototype.initMigrationPersistence = function () {
15
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
16
+ return tslib_1.__generator(this, function (_a) {
17
+ // nothing to do here
18
+ return [2 /*return*/, Promise.resolve()];
19
+ });
20
+ });
21
+ };
22
+ RedisSessionStorageMigrator.prototype.hasMigrationBeenApplied = function (migrationName) {
23
+ var _a;
24
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
25
+ var migrations, found;
26
+ return tslib_1.__generator(this, function (_b) {
27
+ switch (_b.label) {
28
+ case 0: return [4 /*yield*/, this.getMigrationRecords()];
29
+ case 1:
30
+ migrations = _b.sent();
31
+ found = (_a = migrations.get(migrationName)) !== null && _a !== void 0 ? _a : false;
32
+ return [2 /*return*/, Promise.resolve(found)];
33
+ }
34
+ });
35
+ });
36
+ };
37
+ RedisSessionStorageMigrator.prototype.saveAppliedMigration = function (migrationName) {
38
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
39
+ var migrations;
40
+ return tslib_1.__generator(this, function (_a) {
41
+ switch (_a.label) {
42
+ case 0: return [4 /*yield*/, this.getMigrationRecords()];
43
+ case 1:
44
+ migrations = _a.sent();
45
+ migrations.set(migrationName, true);
46
+ this.connection.set(this.options.migrationDBIdentifier, JSON.stringify(Object.fromEntries(migrations)));
47
+ return [2 /*return*/, Promise.resolve()];
48
+ }
49
+ });
50
+ });
51
+ };
52
+ RedisSessionStorageMigrator.prototype.getMigrationRecords = function () {
53
+ return tslib_1.__awaiter(this, void 0, void 0, function () {
54
+ var migrationsRecord, migrations;
55
+ return tslib_1.__generator(this, function (_a) {
56
+ switch (_a.label) {
57
+ case 0: return [4 /*yield*/, this.connection.get(this.options.migrationDBIdentifier)];
58
+ case 1:
59
+ migrationsRecord = _a.sent();
60
+ migrations = new Map();
61
+ if (migrationsRecord) {
62
+ migrations = new Map(Object.entries(JSON.parse(migrationsRecord)));
63
+ }
64
+ return [2 /*return*/, Promise.resolve(migrations)];
65
+ }
66
+ });
67
+ });
68
+ };
69
+ return RedisSessionStorageMigrator;
70
+ }(shopify_app_session_storage_1.AbstractMigrationEngine));
71
+ exports.RedisSessionStorageMigrator = RedisSessionStorageMigrator;
72
+ //# sourceMappingURL=redis-migrator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-migrator.js","sourceRoot":"","sources":["../../src/redis-migrator.ts"],"names":[],"mappings":";;;;AAAA,oFAG8C;AAI9C;IAAiD,uDAGhD;IACC,qCACE,YAA6B,EAC7B,IAAiD;QAAjD,qBAAA,EAAA,SAAiD;QAEjD,6DAA6D;QAC7D,IAAI,CAAC,qBAAqB,GAAG,YAAY,CAAC;eAC1C,kBAAM,YAAY,EAAE,IAAI,CAAC;IAC3B,CAAC;IAEK,8DAAwB,GAA9B;;;gBACE,qBAAqB;gBACrB,sBAAO,OAAO,CAAC,OAAO,EAAE,EAAC;;;KAC1B;IAEK,6DAAuB,GAA7B,UAA8B,aAAqB;;;;;;4BAC9B,qBAAM,IAAI,CAAC,mBAAmB,EAAE,EAAA;;wBAA7C,UAAU,GAAG,SAAgC;wBAC7C,KAAK,GAAG,MAAA,UAAU,CAAC,GAAG,CAAC,aAAa,CAAC,mCAAI,KAAK,CAAC;wBAErD,sBAAO,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,EAAC;;;;KAC/B;IAEK,0DAAoB,GAA1B,UAA2B,aAAqB;;;;;4BAC3B,qBAAM,IAAI,CAAC,mBAAmB,EAAE,EAAA;;wBAA7C,UAAU,GAAG,SAAgC;wBACnD,UAAU,CAAC,GAAG,CAAC,aAAa,EAAE,IAAI,CAAC,CAAC;wBAEpC,IAAI,CAAC,UAAU,CAAC,GAAG,CACjB,IAAI,CAAC,OAAO,CAAC,qBAAqB,EAClC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAC/C,CAAC;wBAEF,sBAAO,OAAO,CAAC,OAAO,EAAE,EAAC;;;;KAC1B;IAEa,yDAAmB,GAAjC;;;;;4BAC2B,qBAAM,IAAI,CAAC,UAAU,CAAC,GAAG,CAChD,IAAI,CAAC,OAAO,CAAC,qBAAqB,CACnC,EAAA;;wBAFK,gBAAgB,GAAG,SAExB;wBACG,UAAU,GAAyB,IAAI,GAAG,EAAE,CAAC;wBACjD,IAAI,gBAAgB,EAAE;4BACpB,UAAU,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC;yBACpE;wBAED,sBAAO,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,EAAC;;;;KACpC;IACH,kCAAC;AAAD,CAAC,AAhDD,CAAiD,qDAAuB,GAgDvE;AAhDY,kEAA2B"}
@@ -1,16 +1,18 @@
1
1
  import { RedisClientOptions } from 'redis';
2
2
  import { Session } from '@shopify/shopify-api';
3
- import { SessionStorage } from '@shopify/shopify-app-session-storage';
3
+ import { SessionStorage, SessionStorageMigratorOptions } from '@shopify/shopify-app-session-storage';
4
4
  export interface RedisSessionStorageOptions extends RedisClientOptions {
5
5
  sessionKeyPrefix: string;
6
6
  onError?: (...args: any[]) => void;
7
+ migratorOptions?: SessionStorageMigratorOptions;
7
8
  }
8
9
  export declare class RedisSessionStorage implements SessionStorage {
9
- private dbUrl;
10
10
  static withCredentials(host: string, db: number, username: string, password: string, opts: Partial<RedisSessionStorageOptions>): RedisSessionStorage;
11
11
  readonly ready: Promise<void>;
12
+ private internalInit;
12
13
  private options;
13
14
  private client;
15
+ private migrator;
14
16
  constructor(dbUrl: URL, opts?: Partial<RedisSessionStorageOptions>);
15
17
  storeSession(session: Session): Promise<boolean>;
16
18
  loadSession(id: string): Promise<Session | undefined>;
@@ -18,10 +20,9 @@ export declare class RedisSessionStorage implements SessionStorage {
18
20
  deleteSessions(ids: string[]): Promise<boolean>;
19
21
  findSessionsByShop(shop: string): Promise<Session[]>;
20
22
  disconnect(): Promise<void>;
21
- private fullKey;
22
23
  private addKeyToShopList;
23
24
  private removeKeyFromShopList;
24
25
  private init;
25
- private migrate;
26
+ private initMigrator;
26
27
  }
27
28
  //# sourceMappingURL=redis.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/redis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,kBAAkB,EAAC,MAAM,OAAO,CAAC;AACvD,OAAO,EAAC,OAAO,EAAC,MAAM,sBAAsB,CAAC;AAC7C,OAAO,EAAC,cAAc,EAAC,MAAM,sCAAsC,CAAC;AAKpE,MAAM,WAAW,0BAA2B,SAAQ,kBAAkB;IACpE,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;CACpC;AAKD,qBAAa,mBAAoB,YAAW,cAAc;IAuBtD,OAAO,CAAC,KAAK;IAtBf,MAAM,CAAC,eAAe,CACpB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,CAAC,0BAA0B,CAAC;IAY3C,SAAgB,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,MAAM,CAAc;gBAGlB,KAAK,EAAE,GAAG,EAClB,IAAI,GAAE,OAAO,CAAC,0BAA0B,CAAM;IASnC,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAWhD,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IAWrD,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAU3C,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAM/C,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAmBpD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAIxC,OAAO,CAAC,OAAO;YAID,gBAAgB;YAiBhB,qBAAqB;YAgBrB,IAAI;YAYJ,OAAO;CAmBtB"}
1
+ {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/redis.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,kBAAkB,EAAC,MAAM,OAAO,CAAC;AACzC,OAAO,EAAC,OAAO,EAAC,MAAM,sBAAsB,CAAC;AAC7C,OAAO,EACL,cAAc,EACd,6BAA6B,EAE9B,MAAM,sCAAsC,CAAC;AAM9C,MAAM,WAAW,0BAA2B,SAAQ,kBAAkB;IACpE,gBAAgB,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,IAAI,CAAC;IACnC,eAAe,CAAC,EAAE,6BAA6B,CAAC;CACjD;AASD,qBAAa,mBAAoB,YAAW,cAAc;IACxD,MAAM,CAAC,eAAe,CACpB,IAAI,EAAE,MAAM,EACZ,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,OAAO,CAAC,0BAA0B,CAAC;IAY3C,SAAgB,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACrC,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,QAAQ,CAAyB;gBAE7B,KAAK,EAAE,GAAG,EAAE,IAAI,GAAE,OAAO,CAAC,0BAA0B,CAAM;IAMzD,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IAWhD,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,CAAC;IAWrD,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAU3C,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,OAAO,CAAC;IAM/C,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAmBpD,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAI1B,gBAAgB;YAiBhB,qBAAqB;YAgBrB,IAAI;YASJ,YAAY;CAiB3B"}