loqkit 0.0.0-stage → 0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Reggie Brooks
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,18 @@
1
- # Temporary Holding Version
1
+ # loqkit
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Small typed locking primitives for JavaScript runtimes.
4
+
5
+ ```ts
6
+ import { withLock } from "loqkit";
7
+
8
+ await withLock(
9
+ { resource: "cache" },
10
+ async ({ resource, lock }) => {
11
+ console.log(`locked ${resource} with ${lock.type}`);
12
+ },
13
+ async ({ resource }) => resource === "cache",
14
+ { key: "cache-refresh" },
15
+ );
16
+ ```
17
+
18
+ The optional predicate runs once before waiting and again after acquisition. The action runs only when both checks pass.
package/package.json CHANGED
@@ -1,6 +1,46 @@
1
1
  {
2
2
  "name": "loqkit",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.1",
4
+ "description": "Small typed locking primitives for JavaScript runtimes.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "exports": {
8
+ ".": {
9
+ "types": "./release/index.d.ts",
10
+ "import": "./release/index.js"
11
+ }
12
+ },
13
+ "types": "./release/index.d.ts",
14
+ "files": [
15
+ "release",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json",
21
+ "check": "bun run typecheck && bun test && bun run build",
22
+ "test": "bun test",
23
+ "typecheck": "tsc -p tsconfig.json --noEmit"
24
+ },
25
+ "keywords": [
26
+ "lock",
27
+ "mutex",
28
+ "concurrency",
29
+ "typescript"
30
+ ],
31
+ "license": "MIT",
32
+ "engines": {
33
+ "node": ">=20"
34
+ },
35
+ "dependencies": {
36
+ "async-mutex": "^0.5.0",
37
+ "zod": "^4.6.7"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^26.6.6",
41
+ "typescript": "^5.9.3"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ }
46
+ }
@@ -0,0 +1,27 @@
1
+ import { z } from "zod";
2
+ export declare const lockOptionsSchema: z.ZodObject<{
3
+ key: z.ZodOptional<z.ZodString>;
4
+ name: z.ZodOptional<z.ZodString>;
5
+ }, z.core.$strict>;
6
+ export type LockOptions = z.input<typeof lockOptionsSchema>;
7
+ export type LockState = "checking" | "acquired";
8
+ export interface LockInfo {
9
+ readonly key: string;
10
+ readonly name: string;
11
+ readonly type: "memory";
12
+ readonly state: LockState;
13
+ }
14
+ export type LockContext<TContext extends object> = Readonly<Omit<TContext, "lock"> & {
15
+ readonly lock: Readonly<LockInfo>;
16
+ }>;
17
+ export type LockAction<TContext extends object, TResult> = (context: LockContext<TContext>) => TResult | PromiseLike<TResult>;
18
+ export type LockPredicate<TContext extends object> = (context: LockContext<TContext>) => boolean | PromiseLike<boolean>;
19
+ export type LockRunResult<TResult> = {
20
+ readonly executed: true;
21
+ readonly value: TResult;
22
+ } | {
23
+ readonly executed: false;
24
+ readonly reason: "predicate-before" | "predicate-after";
25
+ };
26
+ /** Runs an action exclusively. A predicate is checked before and after acquisition. */
27
+ export declare function withLock<TContext extends object, TResult>(context: TContext, action: LockAction<TContext, TResult>, predicate?: LockPredicate<TContext>, suppliedOptions?: LockOptions): Promise<LockRunResult<TResult>>;
@@ -0,0 +1,47 @@
1
+ import { Mutex } from "async-mutex";
2
+ import { z } from "zod";
3
+ export const lockOptionsSchema = z
4
+ .object({
5
+ key: z.string().trim().min(1).optional(),
6
+ name: z.string().trim().min(1).optional(),
7
+ })
8
+ .strict();
9
+ const mutexes = new Map();
10
+ function contextWithLock(context, lock) {
11
+ return Object.freeze({ ...context, lock });
12
+ }
13
+ function defaultKey(context) {
14
+ return JSON.stringify(context);
15
+ }
16
+ /** Runs an action exclusively. A predicate is checked before and after acquisition. */
17
+ export async function withLock(context, action, predicate, suppliedOptions) {
18
+ const options = lockOptionsSchema.parse(suppliedOptions);
19
+ const key = options.key ?? defaultKey(context);
20
+ const name = options.name ?? key;
21
+ const checking = contextWithLock(context, {
22
+ key,
23
+ name,
24
+ type: "memory",
25
+ state: "checking",
26
+ });
27
+ if (predicate && !(await predicate(checking))) {
28
+ return { executed: false, reason: "predicate-before" };
29
+ }
30
+ let mutex = mutexes.get(key);
31
+ if (!mutex) {
32
+ mutex = new Mutex();
33
+ mutexes.set(key, mutex);
34
+ }
35
+ return mutex.runExclusive(async () => {
36
+ const acquired = contextWithLock(context, {
37
+ key,
38
+ name,
39
+ type: "memory",
40
+ state: "acquired",
41
+ });
42
+ if (predicate && !(await predicate(acquired))) {
43
+ return { executed: false, reason: "predicate-after" };
44
+ }
45
+ return { executed: true, value: await action(acquired) };
46
+ });
47
+ }