@stacksjs/database 0.75.115 → 0.75.117
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 +9 -0
- package/dist/row-lock.d.ts +6 -0
- package/dist/row-lock.js +1 -1
- package/package.json +16 -16
package/README.md
CHANGED
|
@@ -57,3 +57,12 @@ For casual chit-chat with others using this package:
|
|
|
57
57
|
The MIT License (MIT). Please see [LICENSE](https://github.com/stacksjs/stacks/tree/main/LICENSE.md) for more information.
|
|
58
58
|
|
|
59
59
|
Made with 💙
|
|
60
|
+
|
|
61
|
+
## Group row locks
|
|
62
|
+
|
|
63
|
+
lockRows(tx, table, ids, { key?, scope? }) from database/runtime acquires
|
|
64
|
+
unique numeric primary keys in deterministic order and returns a map keyed by their
|
|
65
|
+
canonical numeric string value, so padded numeric inputs cannot reverse lock order. Missing scoped rows map to undefined. The caller must supply a
|
|
66
|
+
transaction handle and choose ownership filters; reads, writes and validation
|
|
67
|
+
remain on that handle. This prevents opposite input order from creating a
|
|
68
|
+
lock-order cycle during a transfer, merge or multi-item stock reservation.
|
package/dist/row-lock.d.ts
CHANGED
|
@@ -2,3 +2,9 @@
|
|
|
2
2
|
export declare function assertSqlIdentifier(name: string): void;
|
|
3
3
|
/** Lock a scoped row until the caller's transaction commits. SQLite transactions serialize writers. */
|
|
4
4
|
export declare function lockRow(connection: { unsafe?: (query: string, params?: unknown[]) => PromiseLike<Record<string, unknown>[]> }, table: string, filters: Record<string, string | number | null>): Promise<Record<string, unknown> | undefined>;
|
|
5
|
+
/** Lock a group in one deterministic order, independent of the request's ordering. */
|
|
6
|
+
export declare function lockRows(connection: Parameters<typeof lockRow>[0], table: string, ids: readonly (string | number)[], options?: RowLockGroupOptions): Promise<Map<string, Record<string, unknown> | undefined>>;
|
|
7
|
+
export declare interface RowLockGroupOptions {
|
|
8
|
+
key?: string
|
|
9
|
+
scope?: Record<string, string | number | null>
|
|
10
|
+
}
|
package/dist/row-lock.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{sqlHelpers}from"./sql-helpers";import{getDatabaseDialect}from"./utils";export function assertSqlIdentifier(name){if(!/^[a-z_][a-z_0-9]*$/i.test(name))throw Error(`Invalid SQL identifier: ${name}`)}export async function lockRow(connection,table,filters){const dialect=sqlHelpers(getDatabaseDialect()),quote=dialect.isMysql?"`":'"',identifier=(name)=>{assertSqlIdentifier(name);return`${quote}${name}${quote}`},values=[],conditions=Object.entries(filters).map(([name,value])=>{if(value===null)return`${identifier(name)} IS NULL`;values.push(value);return`${identifier(name)} = ${dialect.param(values.length)}`});if(!conditions.length)throw Error("A row lock requires a scope");const suffix=dialect.isPostgres||dialect.isMysql?" FOR UPDATE":"";if(!connection.unsafe)throw Error("Row locks require a database connection");const rows=await connection.unsafe(`SELECT * FROM ${identifier(table)} WHERE ${conditions.join(" AND ")}${suffix}`,values);if(rows.length>1)throw Error("A row lock requires a unique scope");return rows[0]}
|
|
1
|
+
import{sqlHelpers}from"./sql-helpers";import{getDatabaseDialect}from"./utils";export function assertSqlIdentifier(name){if(!/^[a-z_][a-z_0-9]*$/i.test(name))throw Error(`Invalid SQL identifier: ${name}`)}export async function lockRow(connection,table,filters){const dialect=sqlHelpers(getDatabaseDialect()),quote=dialect.isMysql?"`":'"',identifier=(name)=>{assertSqlIdentifier(name);return`${quote}${name}${quote}`},values=[],conditions=Object.entries(filters).map(([name,value])=>{if(value===null)return`${identifier(name)} IS NULL`;values.push(value);return`${identifier(name)} = ${dialect.param(values.length)}`});if(!conditions.length)throw Error("A row lock requires a scope");const suffix=dialect.isPostgres||dialect.isMysql?" FOR UPDATE":"";if(!connection.unsafe)throw Error("Row locks require a database connection");const rows=await connection.unsafe(`SELECT * FROM ${identifier(table)} WHERE ${conditions.join(" AND ")}${suffix}`,values);if(rows.length>1)throw Error("A row lock requires a unique scope");return rows[0]}export async function lockRows(connection,table,ids,options={}){const key=options.key??"id";assertSqlIdentifier(table);assertSqlIdentifier(key);if(Object.prototype.hasOwnProperty.call(options.scope??{},key))throw Error("The group scope cannot replace its primary key");const unique=new Map;for(const id of ids){if(typeof id!=="number"&&(typeof id!=="string"||!/^\d+$/.test(id)))throw Error("Group locks require numeric primary keys");const number=Number(id);if(!Number.isSafeInteger(number)||number<=0)throw Error("Invalid row key");unique.set(String(number),number)}const rows=new Map;for(const name of[...unique.keys()].sort())rows.set(name,await lockRow(connection,table,{...options.scope,[key]:unique.get(name)}));return rows}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/database",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.117",
|
|
6
6
|
"description": "The Stacks database integration.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -66,26 +66,26 @@
|
|
|
66
66
|
"prepublishOnly": "bun run build"
|
|
67
67
|
},
|
|
68
68
|
"dependencies": {
|
|
69
|
-
"@stacksjs/config": "0.75.
|
|
70
|
-
"@stacksjs/env": "0.75.
|
|
71
|
-
"@stacksjs/error-handling": "0.75.
|
|
72
|
-
"@stacksjs/faker": "^0.75.
|
|
73
|
-
"@stacksjs/features": "0.75.
|
|
74
|
-
"@stacksjs/logging": "0.75.
|
|
75
|
-
"@stacksjs/model-meta": "0.75.
|
|
76
|
-
"@stacksjs/path": "0.75.
|
|
77
|
-
"@stacksjs/query-builder": "^0.75.
|
|
78
|
-
"@stacksjs/security": "0.75.
|
|
79
|
-
"@stacksjs/storage": "0.75.
|
|
80
|
-
"@stacksjs/strings": "0.75.
|
|
69
|
+
"@stacksjs/config": "0.75.117",
|
|
70
|
+
"@stacksjs/env": "0.75.117",
|
|
71
|
+
"@stacksjs/error-handling": "0.75.117",
|
|
72
|
+
"@stacksjs/faker": "^0.75.117",
|
|
73
|
+
"@stacksjs/features": "0.75.117",
|
|
74
|
+
"@stacksjs/logging": "0.75.117",
|
|
75
|
+
"@stacksjs/model-meta": "0.75.117",
|
|
76
|
+
"@stacksjs/path": "0.75.117",
|
|
77
|
+
"@stacksjs/query-builder": "^0.75.117",
|
|
78
|
+
"@stacksjs/security": "0.75.117",
|
|
79
|
+
"@stacksjs/storage": "0.75.117",
|
|
80
|
+
"@stacksjs/strings": "0.75.117",
|
|
81
81
|
"@stacksjs/ts-validation": "^0.5.6",
|
|
82
82
|
"bun-query-builder": "^0.3.5",
|
|
83
83
|
"dynamodb-tooling": "^0.3.6"
|
|
84
84
|
},
|
|
85
85
|
"devDependencies": {
|
|
86
|
-
"@stacksjs/cli": "0.75.
|
|
87
|
-
"@stacksjs/router": "0.75.
|
|
88
|
-
"@stacksjs/utils": "0.75.
|
|
86
|
+
"@stacksjs/cli": "0.75.117",
|
|
87
|
+
"@stacksjs/router": "0.75.117",
|
|
88
|
+
"@stacksjs/utils": "0.75.117",
|
|
89
89
|
"better-dx": "^0.2.24"
|
|
90
90
|
}
|
|
91
91
|
}
|