@global-cache/core 0.5.0 → 0.5.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.
@@ -25,7 +25,6 @@ export declare class Getter {
25
25
  private handleSignatureMismatch;
26
26
  private handleComputed;
27
27
  private handleError;
28
- private handleSignatureChanged;
29
28
  private handleExpired;
30
29
  private handleNoValue;
31
30
  private handleDefault;
@@ -1 +1 @@
1
- {"version":3,"file":"getter.d.ts","sourceRoot":"","sources":["../../src/server/getter.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEtE,KAAK,eAAe,GAAG;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AACjE,KAAK,cAAc,GAAG;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAC3E,KAAK,WAAW,GAAG;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AACxD,KAAK,uBAAuB,GAAG;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3E,MAAM,MAAM,YAAY,GAAG,eAAe,GAAG,cAAc,GAAG,WAAW,GAAG,uBAAuB,CAAC;AAEpG,qBAAa,MAAM;IAEf,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,iBAAiB;gBADjB,cAAc,EAAE,eAAe,EAC/B,iBAAiB,EAAE,kBAAkB;IAIzC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAmDxE,OAAO,CAAC,uBAAuB;IAO/B,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,WAAW;YAIL,sBAAsB;YAetB,aAAa;YAUb,aAAa;YAYb,aAAa;YAMb,eAAe;CAI9B"}
1
+ {"version":3,"file":"getter.d.ts","sourceRoot":"","sources":["../../src/server/getter.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEtE,KAAK,eAAe,GAAG;IAAE,MAAM,EAAE,YAAY,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AACjE,KAAK,cAAc,GAAG;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,SAAS,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAC3E,KAAK,WAAW,GAAG;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AACxD,KAAK,uBAAuB,GAAG;IAAE,MAAM,EAAE,cAAc,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3E,MAAM,MAAM,YAAY,GAAG,eAAe,GAAG,cAAc,GAAG,WAAW,GAAG,uBAAuB,CAAC;AAEpG,qBAAa,MAAM;IAEf,OAAO,CAAC,cAAc;IACtB,OAAO,CAAC,iBAAiB;gBADjB,cAAc,EAAE,eAAe,EAC/B,iBAAiB,EAAE,kBAAkB;IAIzC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAwCxE,OAAO,CAAC,uBAAuB;IAO/B,OAAO,CAAC,cAAc;IAItB,OAAO,CAAC,WAAW;YAIL,aAAa;YAWb,aAAa;YAwCb,aAAa;YAMb,eAAe;CAI9B"}
@@ -28,31 +28,21 @@ class Getter {
28
28
  ? this.handleComputed(valueInfo)
29
29
  : this.handleError(valueInfo);
30
30
  }
31
- // load from persistent storage
32
- if (!valueInfo && ttl) {
33
- valueInfo = await this.persistentStorage.load(key);
34
- if (valueInfo) {
35
- const signatureMismatch = (0, sig_1.checkSignature)(key, valueInfo.sig, sig);
36
- // eslint-disable-next-line max-depth
37
- if (signatureMismatch) {
38
- return this.handleSignatureChanged(valueInfo, sig, signatureMismatch);
39
- }
40
- }
31
+ // no value in memory -- claim the key atomically before touching persistent storage,
32
+ // so concurrent callers for the same key can't all decide to recompute independently
33
+ if (!valueInfo) {
34
+ return this.handleNoValue(key, sig, ttl);
41
35
  }
42
36
  // check expired
43
- if (valueInfo?.state === 'computed' && valueInfo.persistent && ttl) {
37
+ if (valueInfo.state === 'computed' && valueInfo.persistent && ttl) {
44
38
  if ((0, ttl_1.isExpired)(valueInfo.computedAt, ttl)) {
45
39
  return this.handleExpired(valueInfo, ttl);
46
40
  }
47
41
  }
48
42
  // normal cache hit
49
- if (valueInfo?.state === 'computed') {
43
+ if (valueInfo.state === 'computed') {
50
44
  return this.handleComputed(valueInfo);
51
45
  }
52
- // no value
53
- if (!valueInfo) {
54
- return this.handleNoValue(key, sig, ttl);
55
- }
56
46
  return this.handleDefault(valueInfo);
57
47
  }
58
48
  handleSignatureMismatch(key, signatureMismatch) {
@@ -64,16 +54,6 @@ class Getter {
64
54
  handleError(valueInfo) {
65
55
  return { result: 'error', message: valueInfo.errorMessage || 'Unknown error' };
66
56
  }
67
- async handleSignatureChanged(loadedValueInfo, newSig, { field }) {
68
- await this.updateValueInfo(loadedValueInfo, {
69
- state: 'computing',
70
- sig: newSig,
71
- prevValue: loadedValueInfo.value,
72
- value: undefined,
73
- computedAt: undefined,
74
- });
75
- return { result: 'cache-miss', message: `signature changed: ${field}` };
76
- }
77
57
  async handleExpired(valueInfo, ttl) {
78
58
  await this.updateValueInfo(valueInfo, {
79
59
  state: 'computing',
@@ -83,16 +63,42 @@ class Getter {
83
63
  });
84
64
  return { result: 'cache-miss', message: `expired: ${ttl}` };
85
65
  }
66
+ // eslint-disable-next-line visual/complexity, max-statements
86
67
  async handleNoValue(key, sig, ttl) {
87
- const valueInfo = { key, state: 'computing', sig, persistent: Boolean(ttl) };
88
- const claimed = this.testRunStorage.claimForComputing(valueInfo);
68
+ const placeholder = {
69
+ key,
70
+ state: 'computing',
71
+ sig,
72
+ persistent: Boolean(ttl),
73
+ };
74
+ const claimed = this.testRunStorage.claimForComputing(placeholder);
89
75
  if (!claimed) {
90
76
  const resolved = await this.testRunStorage.waitForComputed(key);
91
77
  return resolved.state === 'computed'
92
78
  ? this.handleComputed(resolved)
93
79
  : this.handleError(resolved);
94
80
  }
95
- return { result: 'cache-miss', message: 'no cached value' };
81
+ if (!ttl)
82
+ return { result: 'cache-miss', message: 'no cached value' };
83
+ // We own the 'computing' slot now, so no other concurrent caller can also decide
84
+ // to recompute this key while we consult persistent storage.
85
+ const persisted = await this.persistentStorage.load(key);
86
+ if (!persisted)
87
+ return { result: 'cache-miss', message: 'no cached value' };
88
+ const signatureMismatch = (0, sig_1.checkSignature)(key, persisted.sig, sig);
89
+ if (signatureMismatch) {
90
+ placeholder.prevValue = persisted.value;
91
+ return { result: 'cache-miss', message: `signature changed: ${signatureMismatch.field}` };
92
+ }
93
+ if ((0, ttl_1.isExpired)(persisted.computedAt, ttl)) {
94
+ placeholder.prevValue = persisted.value;
95
+ return { result: 'cache-miss', message: `expired: ${ttl}` };
96
+ }
97
+ // Persistent value is fresh and valid -- adopt it as computed, update the map entry
98
+ // (already resident via claimForComputing) and notify any waiters.
99
+ Object.assign(placeholder, persisted);
100
+ await this.testRunStorage.save(placeholder, { notify: true });
101
+ return this.handleComputed(placeholder);
96
102
  }
97
103
  async handleDefault(valueInfo) {
98
104
  const message = `old state: ${valueInfo.state}`;
@@ -1 +1 @@
1
- {"version":3,"file":"getter.js","sourceRoot":"","sources":["../../src/server/getter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"getter.js","sourceRoot":"","sources":["../../src/server/getter.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@global-cache/core",
3
3
  "description": "Key-value cache for sharing data between parallel workers.",
4
- "version": "0.5.0",
4
+ "version": "0.5.1",
5
5
  "type": "commonjs",
6
6
  "main": "./dist/index.js",
7
7
  "types": "./dist/index.d.ts",
@@ -39,35 +39,24 @@ export class Getter {
39
39
  : this.handleError(valueInfo);
40
40
  }
41
41
 
42
- // load from persistent storage
43
- if (!valueInfo && ttl) {
44
- valueInfo = await this.persistentStorage.load(key);
45
- if (valueInfo) {
46
- const signatureMismatch = checkSignature(key, valueInfo.sig, sig);
47
- // eslint-disable-next-line max-depth
48
- if (signatureMismatch) {
49
- return this.handleSignatureChanged(valueInfo, sig, signatureMismatch);
50
- }
51
- }
42
+ // no value in memory -- claim the key atomically before touching persistent storage,
43
+ // so concurrent callers for the same key can't all decide to recompute independently
44
+ if (!valueInfo) {
45
+ return this.handleNoValue(key, sig, ttl);
52
46
  }
53
47
 
54
48
  // check expired
55
- if (valueInfo?.state === 'computed' && valueInfo.persistent && ttl) {
49
+ if (valueInfo.state === 'computed' && valueInfo.persistent && ttl) {
56
50
  if (isExpired(valueInfo.computedAt, ttl)) {
57
51
  return this.handleExpired(valueInfo, ttl);
58
52
  }
59
53
  }
60
54
 
61
55
  // normal cache hit
62
- if (valueInfo?.state === 'computed') {
56
+ if (valueInfo.state === 'computed') {
63
57
  return this.handleComputed(valueInfo);
64
58
  }
65
59
 
66
- // no value
67
- if (!valueInfo) {
68
- return this.handleNoValue(key, sig, ttl);
69
- }
70
-
71
60
  return this.handleDefault(valueInfo);
72
61
  }
73
62
 
@@ -86,21 +75,6 @@ export class Getter {
86
75
  return { result: 'error', message: valueInfo.errorMessage || 'Unknown error' };
87
76
  }
88
77
 
89
- private async handleSignatureChanged(
90
- loadedValueInfo: TestRunValueInfo,
91
- newSig: string,
92
- { field }: SignatureMismatch,
93
- ): Promise<GetterCacheMiss> {
94
- await this.updateValueInfo(loadedValueInfo, {
95
- state: 'computing',
96
- sig: newSig,
97
- prevValue: loadedValueInfo.value,
98
- value: undefined,
99
- computedAt: undefined,
100
- });
101
- return { result: 'cache-miss', message: `signature changed: ${field}` };
102
- }
103
-
104
78
  private async handleExpired(valueInfo: TestRunValueInfo, ttl: number): Promise<GetterCacheMiss> {
105
79
  await this.updateValueInfo(valueInfo, {
106
80
  state: 'computing',
@@ -111,16 +85,45 @@ export class Getter {
111
85
  return { result: 'cache-miss', message: `expired: ${ttl}` };
112
86
  }
113
87
 
88
+ // eslint-disable-next-line visual/complexity, max-statements
114
89
  private async handleNoValue(key: string, sig: string, ttl?: number): Promise<GetterResult> {
115
- const valueInfo: TestRunValueInfo = { key, state: 'computing', sig, persistent: Boolean(ttl) };
116
- const claimed = this.testRunStorage.claimForComputing(valueInfo);
90
+ const placeholder: TestRunValueInfo = {
91
+ key,
92
+ state: 'computing',
93
+ sig,
94
+ persistent: Boolean(ttl),
95
+ };
96
+ const claimed = this.testRunStorage.claimForComputing(placeholder);
117
97
  if (!claimed) {
118
98
  const resolved = await this.testRunStorage.waitForComputed(key);
119
99
  return resolved.state === 'computed'
120
100
  ? this.handleComputed(resolved)
121
101
  : this.handleError(resolved);
122
102
  }
123
- return { result: 'cache-miss', message: 'no cached value' };
103
+
104
+ if (!ttl) return { result: 'cache-miss', message: 'no cached value' };
105
+
106
+ // We own the 'computing' slot now, so no other concurrent caller can also decide
107
+ // to recompute this key while we consult persistent storage.
108
+ const persisted = await this.persistentStorage.load(key);
109
+ if (!persisted) return { result: 'cache-miss', message: 'no cached value' };
110
+
111
+ const signatureMismatch = checkSignature(key, persisted.sig, sig);
112
+ if (signatureMismatch) {
113
+ placeholder.prevValue = persisted.value;
114
+ return { result: 'cache-miss', message: `signature changed: ${signatureMismatch.field}` };
115
+ }
116
+
117
+ if (isExpired(persisted.computedAt, ttl)) {
118
+ placeholder.prevValue = persisted.value;
119
+ return { result: 'cache-miss', message: `expired: ${ttl}` };
120
+ }
121
+
122
+ // Persistent value is fresh and valid -- adopt it as computed, update the map entry
123
+ // (already resident via claimForComputing) and notify any waiters.
124
+ Object.assign(placeholder, persisted);
125
+ await this.testRunStorage.save(placeholder, { notify: true });
126
+ return this.handleComputed(placeholder);
124
127
  }
125
128
 
126
129
  private async handleDefault(valueInfo: TestRunValueInfo): Promise<GetterCacheMiss> {