sandly 0.5.3 → 1.0.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.
Files changed (4) hide show
  1. package/README.md +65 -14
  2. package/dist/index.d.ts +477 -400
  3. package/dist/index.js +245 -146
  4. package/package.json +68 -74
package/dist/index.js CHANGED
@@ -1,3 +1,158 @@
1
+ //#region src/layer.ts
2
+ /**
3
+ * The type ID for the Layer interface.
4
+ */
5
+ const LayerTypeId = Symbol.for("sandly/Layer");
6
+ /**
7
+ * Creates a new dependency layer that encapsulates a set of dependency registrations.
8
+ * Layers are the primary building blocks for organizing and composing dependency injection setups.
9
+ *
10
+ * @template TRequires - The union of dependency tags this layer requires from other layers or external setup
11
+ * @template TProvides - The union of dependency tags this layer registers/provides
12
+ *
13
+ * @param register - Function that performs the dependency registrations. Receives a container.
14
+ * @returns The layer instance.
15
+ *
16
+ * @example Simple layer
17
+ * ```typescript
18
+ * import { layer, Tag } from 'sandly';
19
+ *
20
+ * class DatabaseService extends Tag.Service('DatabaseService') {
21
+ * constructor(private url: string = 'sqlite://memory') {}
22
+ * query() { return 'data'; }
23
+ * }
24
+ *
25
+ * // Layer that provides DatabaseService, requires nothing
26
+ * const databaseLayer = layer<never, typeof DatabaseService>((container) =>
27
+ * container.register(DatabaseService, () => new DatabaseService())
28
+ * );
29
+ *
30
+ * // Usage
31
+ * const dbLayerInstance = databaseLayer;
32
+ * ```
33
+ *
34
+ * @example Complex application layer structure
35
+ * ```typescript
36
+ * // Configuration layer
37
+ * const configLayer = layer<never, typeof ConfigTag>((container) =>
38
+ * container.register(ConfigTag, () => loadConfig())
39
+ * );
40
+ *
41
+ * // Infrastructure layer (requires config)
42
+ * const infraLayer = layer<typeof ConfigTag, typeof DatabaseService | typeof CacheService>(
43
+ * (container) =>
44
+ * container
45
+ * .register(DatabaseService, async (ctx) => new DatabaseService(await ctx.resolve(ConfigTag)))
46
+ * .register(CacheService, async (ctx) => new CacheService(await ctx.resolve(ConfigTag)))
47
+ * );
48
+ *
49
+ * // Service layer (requires infrastructure)
50
+ * const serviceLayer = layer<typeof DatabaseService | typeof CacheService, typeof UserService>(
51
+ * (container) =>
52
+ * container.register(UserService, async (ctx) =>
53
+ * new UserService(await ctx.resolve(DatabaseService), await ctx.resolve(CacheService))
54
+ * )
55
+ * );
56
+ *
57
+ * // Compose the complete application
58
+ * const appLayer = serviceLayer.provide(infraLayer).provide(configLayer);
59
+ * ```
60
+ */
61
+ function layer(register) {
62
+ const layerImpl = {
63
+ register: (container) => register(container),
64
+ provide(dependency$1) {
65
+ return createProvidedLayer(dependency$1, layerImpl);
66
+ },
67
+ provideMerge(dependency$1) {
68
+ return createComposedLayer(dependency$1, layerImpl);
69
+ },
70
+ merge(other) {
71
+ return createMergedLayer(layerImpl, other);
72
+ }
73
+ };
74
+ return layerImpl;
75
+ }
76
+ /**
77
+ * Internal function to create a provided layer from two layers.
78
+ * This implements the `.provide()` method logic - only exposes target layer's provisions.
79
+ *
80
+ * @internal
81
+ */
82
+ function createProvidedLayer(dependency$1, target) {
83
+ return createComposedLayer(dependency$1, target);
84
+ }
85
+ /**
86
+ * Internal function to create a composed layer from two layers.
87
+ * This implements the `.provideMerge()` method logic - exposes both layers' provisions.
88
+ *
89
+ * @internal
90
+ */
91
+ function createComposedLayer(dependency$1, target) {
92
+ return layer((container) => {
93
+ const containerWithDependency = dependency$1.register(
94
+ container
95
+ // The type
96
+ // IContainer<TRequires1 | TProvides1 | Exclude<TRequires2, TProvides1> | TContainer>
97
+ // can be simplified to
98
+ // IContainer<TRequires1 | TRequires2 | TProvides1 | TContainer>
99
+ );
100
+ return target.register(containerWithDependency);
101
+ });
102
+ }
103
+ /**
104
+ * Internal function to create a merged layer from two layers.
105
+ * This implements the `.merge()` method logic.
106
+ *
107
+ * @internal
108
+ */
109
+ function createMergedLayer(layer1, layer2) {
110
+ return layer((container) => {
111
+ const container1 = layer1.register(container);
112
+ const container2 = layer2.register(container1);
113
+ return container2;
114
+ });
115
+ }
116
+ /**
117
+ * Utility object containing helper functions for working with layers.
118
+ */
119
+ const Layer = {
120
+ empty() {
121
+ return layer((container) => container);
122
+ },
123
+ mergeAll(...layers) {
124
+ return layers.reduce((acc, layer$1) => acc.merge(layer$1));
125
+ },
126
+ merge(layer1, layer2) {
127
+ return layer1.merge(layer2);
128
+ }
129
+ };
130
+
131
+ //#endregion
132
+ //#region src/constant.ts
133
+ /**
134
+ * Creates a layer that provides a constant value for a given tag.
135
+ *
136
+ * @param tag - The value tag to provide
137
+ * @param constantValue - The constant value to provide
138
+ * @returns A layer with no dependencies that provides the constant value
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * const ApiKey = Tag.of('ApiKey')<string>();
143
+ * const DatabaseUrl = Tag.of('DatabaseUrl')<string>();
144
+ *
145
+ * const apiKey = constant(ApiKey, 'my-secret-key');
146
+ * const dbUrl = constant(DatabaseUrl, 'postgresql://localhost:5432/myapp');
147
+ *
148
+ * const config = Layer.merge(apiKey, dbUrl);
149
+ * ```
150
+ */
151
+ function constant(tag, constantValue) {
152
+ return layer((container) => container.register(tag, () => constantValue));
153
+ }
154
+
155
+ //#endregion
1
156
  //#region src/utils/object.ts
2
157
  function hasKey(obj, key) {
3
158
  return obj !== void 0 && obj !== null && (typeof obj === "object" || typeof obj === "function") && key in obj;
@@ -45,12 +200,6 @@ const Tag = {
45
200
  [TagTypeKey]: void 0
46
201
  });
47
202
  },
48
- for: () => {
49
- return {
50
- [ValueTagIdKey]: Symbol(),
51
- [TagTypeKey]: void 0
52
- };
53
- },
54
203
  Service: (id) => {
55
204
  class Tagged {
56
205
  static [ServiceTagIdKey] = id;
@@ -97,29 +246,39 @@ var SandlyError = class SandlyError extends Error {
97
246
  super(message, { cause });
98
247
  this.name = this.constructor.name;
99
248
  this.detail = detail;
100
- if (cause instanceof Error && cause.stack !== void 0) this.stack = `${this.stack}\nCaused by: ${cause.stack}`;
249
+ if (cause instanceof Error && cause.stack !== void 0) this.stack = `${this.stack ?? ""}\nCaused by: ${cause.stack}`;
101
250
  }
102
251
  static ensure(error) {
103
252
  return error instanceof SandlyError ? error : new SandlyError("An unknown error occurred", { cause: error });
104
253
  }
105
254
  dump() {
106
- const cause = this.cause instanceof SandlyError ? this.cause.dump().error : this.cause;
107
- const result = {
108
- name: this.name,
109
- message: this.message,
110
- cause,
111
- detail: this.detail ?? {}
112
- };
113
255
  return {
114
256
  name: this.name,
115
- message: result.message,
257
+ message: this.message,
116
258
  stack: this.stack,
117
- error: result
259
+ detail: this.detail ?? {},
260
+ cause: this.dumpCause(this.cause)
118
261
  };
119
262
  }
120
263
  dumps() {
121
264
  return JSON.stringify(this.dump());
122
265
  }
266
+ /**
267
+ * Recursively extract cause chain from any Error.
268
+ * Handles both AppError (with dump()) and plain Errors (with cause property).
269
+ */
270
+ dumpCause(cause) {
271
+ if (cause instanceof SandlyError) return cause.dump();
272
+ if (cause instanceof Error) {
273
+ const result = {
274
+ name: cause.name,
275
+ message: cause.message
276
+ };
277
+ if ("cause" in cause && cause.cause !== void 0) result.cause = this.dumpCause(cause.cause);
278
+ return result;
279
+ }
280
+ return cause;
281
+ }
123
282
  };
124
283
  /**
125
284
  * Error thrown when attempting to register a dependency that has already been instantiated.
@@ -370,7 +529,7 @@ const ContainerTypeId = Symbol.for("sandly/Container");
370
529
  * at the type level, ensuring that only registered dependencies can be retrieved
371
530
  * and preventing runtime errors.
372
531
  *
373
- * @template TReg - Union type of all registered dependency tags in this container
532
+ * @template TTags - Union type of all registered dependency tags in this container
374
533
  *
375
534
  * @example Basic usage with service tags
376
535
  * ```typescript
@@ -436,17 +595,17 @@ var Container = class Container {
436
595
  * Ensures singleton behavior and supports concurrent access.
437
596
  * @internal
438
597
  */
439
- cache = /* @__PURE__ */ new Map();
598
+ cache = new Map();
440
599
  /**
441
600
  * Factory functions for creating dependency instances.
442
601
  * @internal
443
602
  */
444
- factories = /* @__PURE__ */ new Map();
603
+ factories = new Map();
445
604
  /**
446
605
  * Finalizer functions for cleaning up dependencies when the container is destroyed.
447
606
  * @internal
448
607
  */
449
- finalizers = /* @__PURE__ */ new Map();
608
+ finalizers = new Map();
450
609
  /**
451
610
  * Flag indicating whether this container has been destroyed.
452
611
  * @internal
@@ -797,129 +956,93 @@ var Container = class Container {
797
956
  };
798
957
 
799
958
  //#endregion
800
- //#region src/layer.ts
801
- /**
802
- * The type ID for the Layer interface.
803
- */
804
- const LayerTypeId = Symbol.for("sandly/Layer");
959
+ //#region src/dependency.ts
805
960
  /**
806
- * Creates a new dependency layer that encapsulates a set of dependency registrations.
807
- * Layers are the primary building blocks for organizing and composing dependency injection setups.
961
+ * Creates a layer that provides a single dependency with inferred requirements.
808
962
  *
809
- * @template TRequires - The union of dependency tags this layer requires from other layers or external setup
810
- * @template TProvides - The union of dependency tags this layer registers/provides
963
+ * This is a simplified alternative to `layer()` for the common case of defining
964
+ * a single dependency. Unlike `service()` and `autoService()`, this works with
965
+ * any tag type (ServiceTag or ValueTag) and doesn't require extending `Tag.Service()`.
811
966
  *
812
- * @param register - Function that performs the dependency registrations. Receives a container.
813
- * @returns The layer instance.
967
+ * Requirements are passed as an optional array of tags, allowing TypeScript to infer
968
+ * both the tag type and the requirements automatically - no explicit type
969
+ * parameters needed.
814
970
  *
815
- * @example Simple layer
971
+ * @param tag - The tag (ServiceTag or ValueTag) that identifies this dependency
972
+ * @param spec - Factory function or lifecycle object for creating the dependency
973
+ * @param requirements - Optional array of dependency tags this dependency requires (defaults to [])
974
+ * @returns A layer that requires the specified dependencies and provides the tag
975
+ *
976
+ * @example Simple dependency without requirements
816
977
  * ```typescript
817
- * import { layer, Tag } from 'sandly';
978
+ * const Config = Tag.of('Config')<{ apiUrl: string }>();
818
979
  *
819
- * class DatabaseService extends Tag.Service('DatabaseService') {
820
- * constructor(private url: string = 'sqlite://memory') {}
821
- * query() { return 'data'; }
822
- * }
980
+ * // No requirements - can omit the array
981
+ * const configDep = dependency(Config, () => ({
982
+ * apiUrl: process.env.API_URL!
983
+ * }));
984
+ * ```
823
985
  *
824
- * // Layer that provides DatabaseService, requires nothing
825
- * const databaseLayer = layer<never, typeof DatabaseService>((container) =>
826
- * container.register(DatabaseService, () => new DatabaseService())
986
+ * @example Dependency with requirements
987
+ * ```typescript
988
+ * const database = dependency(
989
+ * Database,
990
+ * async (ctx) => {
991
+ * const config = await ctx.resolve(Config);
992
+ * const logger = await ctx.resolve(Logger);
993
+ * logger.info('Creating database connection');
994
+ * return createDb(config.DATABASE);
995
+ * },
996
+ * [Config, Logger]
827
997
  * );
828
- *
829
- * // Usage
830
- * const dbLayerInstance = databaseLayer;
831
998
  * ```
832
999
  *
833
- * @example Complex application layer structure
1000
+ * @example Dependency with lifecycle (create + cleanup)
834
1001
  * ```typescript
835
- * // Configuration layer
836
- * const configLayer = layer<never, typeof ConfigTag>((container) =>
837
- * container.register(ConfigTag, () => loadConfig())
1002
+ * const database = dependency(
1003
+ * Database,
1004
+ * {
1005
+ * create: async (ctx) => {
1006
+ * const config = await ctx.resolve(Config);
1007
+ * const logger = await ctx.resolve(Logger);
1008
+ * logger.info('Creating database connection');
1009
+ * return await createDb(config.DATABASE);
1010
+ * },
1011
+ * cleanup: async (db) => {
1012
+ * await disconnectDb(db);
1013
+ * },
1014
+ * },
1015
+ * [Config, Logger]
838
1016
  * );
1017
+ * ```
839
1018
  *
840
- * // Infrastructure layer (requires config)
841
- * const infraLayer = layer<typeof ConfigTag, typeof DatabaseService | typeof CacheService>(
1019
+ * @example Comparison with layer()
1020
+ * ```typescript
1021
+ * // Using layer() - verbose, requires explicit type parameters
1022
+ * const database = layer<typeof Config | typeof Logger, typeof Database>(
842
1023
  * (container) =>
843
- * container
844
- * .register(DatabaseService, async (ctx) => new DatabaseService(await ctx.resolve(ConfigTag)))
845
- * .register(CacheService, async (ctx) => new CacheService(await ctx.resolve(ConfigTag)))
1024
+ * container.register(Database, async (ctx) => {
1025
+ * const config = await ctx.resolve(Config);
1026
+ * return createDb(config.DATABASE);
1027
+ * })
846
1028
  * );
847
1029
  *
848
- * // Service layer (requires infrastructure)
849
- * const serviceLayer = layer<typeof DatabaseService | typeof CacheService, typeof UserService>(
850
- * (container) =>
851
- * container.register(UserService, async (ctx) =>
852
- * new UserService(await ctx.resolve(DatabaseService), await ctx.resolve(CacheService))
853
- * )
1030
+ * // Using dependency() - cleaner, fully inferred types
1031
+ * const database = dependency(
1032
+ * Database,
1033
+ * async (ctx) => {
1034
+ * const config = await ctx.resolve(Config);
1035
+ * return createDb(config.DATABASE);
1036
+ * },
1037
+ * [Config, Logger]
854
1038
  * );
855
- *
856
- * // Compose the complete application
857
- * const appLayer = serviceLayer.provide(infraLayer).provide(configLayer);
858
1039
  * ```
859
1040
  */
860
- function layer(register) {
861
- const layerImpl = {
862
- register: (container) => register(container),
863
- provide(dependency) {
864
- return createProvidedLayer(dependency, layerImpl);
865
- },
866
- provideMerge(dependency) {
867
- return createComposedLayer(dependency, layerImpl);
868
- },
869
- merge(other) {
870
- return createMergedLayer(layerImpl, other);
871
- }
872
- };
873
- return layerImpl;
874
- }
875
- /**
876
- * Internal function to create a provided layer from two layers.
877
- * This implements the `.provide()` method logic - only exposes target layer's provisions.
878
- *
879
- * @internal
880
- */
881
- function createProvidedLayer(dependency, target) {
882
- return createComposedLayer(dependency, target);
883
- }
884
- /**
885
- * Internal function to create a composed layer from two layers.
886
- * This implements the `.provideMerge()` method logic - exposes both layers' provisions.
887
- *
888
- * @internal
889
- */
890
- function createComposedLayer(dependency, target) {
1041
+ function dependency(tag, spec, requirements) {
891
1042
  return layer((container) => {
892
- const containerWithDependency = dependency.register(container);
893
- return target.register(containerWithDependency);
894
- });
895
- }
896
- /**
897
- * Internal function to create a merged layer from two layers.
898
- * This implements the `.merge()` method logic.
899
- *
900
- * @internal
901
- */
902
- function createMergedLayer(layer1, layer2) {
903
- return layer((container) => {
904
- const container1 = layer1.register(container);
905
- const container2 = layer2.register(container1);
906
- return container2;
1043
+ return container.register(tag, spec);
907
1044
  });
908
1045
  }
909
- /**
910
- * Utility object containing helper functions for working with layers.
911
- */
912
- const Layer = {
913
- empty() {
914
- return layer((container) => container);
915
- },
916
- mergeAll(...layers) {
917
- return layers.reduce((acc, layer$1) => acc.merge(layer$1));
918
- },
919
- merge(layer1, layer2) {
920
- return layer1.merge(layer2);
921
- }
922
- };
923
1046
 
924
1047
  //#endregion
925
1048
  //#region src/scoped-container.ts
@@ -1199,28 +1322,4 @@ function autoService(tag, spec) {
1199
1322
  }
1200
1323
 
1201
1324
  //#endregion
1202
- //#region src/value.ts
1203
- /**
1204
- * Creates a layer that provides a constant value for a given tag.
1205
- *
1206
- * @param tag - The value tag to provide
1207
- * @param constantValue - The constant value to provide
1208
- * @returns A layer with no dependencies that provides the constant value
1209
- *
1210
- * @example
1211
- * ```typescript
1212
- * const ApiKey = Tag.of('ApiKey')<string>();
1213
- * const DatabaseUrl = Tag.of('DatabaseUrl')<string>();
1214
- *
1215
- * const apiKey = value(ApiKey, 'my-secret-key');
1216
- * const dbUrl = value(DatabaseUrl, 'postgresql://localhost:5432/myapp');
1217
- *
1218
- * const config = Layer.merge(apiKey, dbUrl);
1219
- * ```
1220
- */
1221
- function value(tag, constantValue) {
1222
- return layer((container) => container.register(tag, () => constantValue));
1223
- }
1224
-
1225
- //#endregion
1226
- export { CircularDependencyError, Container, ContainerDestroyedError, DependencyAlreadyInstantiatedError, DependencyCreationError, DependencyFinalizationError, InjectSource, Layer, SandlyError, ScopedContainer, Tag, UnknownDependencyError, autoService, layer, service, value };
1325
+ export { CircularDependencyError, Container, ContainerDestroyedError, DependencyAlreadyInstantiatedError, DependencyCreationError, DependencyFinalizationError, InjectSource, Layer, SandlyError, ScopedContainer, Tag, UnknownDependencyError, autoService, constant, dependency, layer, service };
package/package.json CHANGED
@@ -1,75 +1,69 @@
1
1
  {
2
- "name": "sandly",
3
- "version": "0.5.3",
4
- "keywords": [
5
- "typescript",
6
- "sandly",
7
- "dependency-injection",
8
- "dependency-inversion",
9
- "injection",
10
- "di",
11
- "inversion-of-control",
12
- "ioc",
13
- "container",
14
- "layer",
15
- "service",
16
- "aws",
17
- "lambda",
18
- "aws-lambda"
19
- ],
20
- "homepage": "https://github.com/borisrakovan/sandly",
21
- "bugs": {
22
- "url": "https://github.com/borisrakovan/sandly/issues"
23
- },
24
- "author": "Boris Rakovan <b.rakovan@gmail.com> (https://github.com/borisrakovan)",
25
- "repository": {
26
- "type": "git",
27
- "url": "git+https://github.com/borisrakovan/sandly.git"
28
- },
29
- "license": "MIT",
30
- "files": [
31
- "dist"
32
- ],
33
- "type": "module",
34
- "main": "dist/index.js",
35
- "types": "dist/index.d.ts",
36
- "exports": {
37
- ".": {
38
- "types": "./dist/index.d.ts",
39
- "import": "./dist/index.js"
40
- }
41
- },
42
- "devDependencies": {
43
- "@changesets/cli": "^2.29.5",
44
- "@eslint/js": "^9.26.0",
45
- "@types/aws-lambda": "^8.10.149",
46
- "@types/node": "^22.15.3",
47
- "@types/node-fetch": "^2.6.12",
48
- "dotenv": "^16.5.0",
49
- "esbuild": "^0.25.3",
50
- "eslint": "^9.26.0",
51
- "node-fetch": "^3.3.2",
52
- "prettier": "^3.5.3",
53
- "prettier-plugin-organize-imports": "^4.1.0",
54
- "ts-node": "^10.9.2",
55
- "tsdown": "^0.14.1",
56
- "tsx": "^4.19.4",
57
- "typescript": "^5.8.3",
58
- "typescript-eslint": "^8.38.0",
59
- "vite-tsconfig-paths": "^5.1.4",
60
- "vitest": "^3.1.3",
61
- "zod": "^4.0.15"
62
- },
63
- "scripts": {
64
- "build": "tsdown",
65
- "clean": "rm -rf dist",
66
- "watch": "tsc --watch",
67
- "format": "prettier --write \"{src,test}/**/*.ts\"",
68
- "format:check": "prettier --check \"{src,test}/**/*.ts\"",
69
- "lint": "eslint . --fix",
70
- "lint:check": "eslint . --max-warnings=0",
71
- "type:check": "tsc --noEmit",
72
- "test": "vitest run",
73
- "release": "changeset version && changeset publish"
74
- }
75
- }
2
+ "name": "sandly",
3
+ "version": "1.0.1",
4
+ "keywords": [
5
+ "typescript",
6
+ "sandly",
7
+ "dependency-injection",
8
+ "dependency-inversion",
9
+ "injection",
10
+ "di",
11
+ "inversion-of-control",
12
+ "ioc",
13
+ "container",
14
+ "layer",
15
+ "service",
16
+ "aws",
17
+ "lambda",
18
+ "aws-lambda"
19
+ ],
20
+ "homepage": "https://github.com/borisrakovan/sandly",
21
+ "bugs": {
22
+ "url": "https://github.com/borisrakovan/sandly/issues"
23
+ },
24
+ "author": "Boris Rakovan <b.rakovan@gmail.com> (https://github.com/borisrakovan)",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/borisrakovan/sandly.git"
28
+ },
29
+ "license": "MIT",
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "type": "module",
34
+ "main": "dist/index.js",
35
+ "types": "dist/index.d.ts",
36
+ "exports": {
37
+ ".": {
38
+ "types": "./dist/index.d.ts",
39
+ "import": "./dist/index.js"
40
+ }
41
+ },
42
+ "scripts": {
43
+ "build": "tsdown",
44
+ "clean": "rm -rf dist",
45
+ "watch": "tsc --watch",
46
+ "format": "prettier --write \"{src,test}/**/*.ts\"",
47
+ "format:check": "prettier --check \"{src,test}/**/*.ts\"",
48
+ "lint": "eslint . --fix",
49
+ "lint:check": "eslint . --max-warnings=0",
50
+ "type:check": "tsc --noEmit",
51
+ "test": "vitest run",
52
+ "tag": "git tag v$(node -p \"require('./package.json').version\") && git push --tags",
53
+ "release": "changeset version && changeset publish"
54
+ },
55
+ "devDependencies": {
56
+ "@changesets/cli": "^2.29.5",
57
+ "@eslint/js": "^9.26.0",
58
+ "@types/node": "^22.15.3",
59
+ "eslint": "^9.26.0",
60
+ "prettier": "^3.5.3",
61
+ "prettier-plugin-organize-imports": "^4.1.0",
62
+ "tsdown": "0.11.7",
63
+ "typescript": "^5.8.3",
64
+ "typescript-eslint": "^8.38.0",
65
+ "vite-tsconfig-paths": "^5.1.4",
66
+ "vitest": "^3.1.3"
67
+ },
68
+ "packageManager": "pnpm@9.13.0+sha512.beb9e2a803db336c10c9af682b58ad7181ca0fbd0d4119f2b33d5f2582e96d6c0d93c85b23869295b765170fbdaa92890c0da6ada457415039769edf3c959efe"
69
+ }