@rezwanul7/settings 0.0.0-stage → 0.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.
@@ -0,0 +1,148 @@
1
+ import { S as SettingsStore, O as Owner, a as StoredOverrides } from './store-B4pWek75.js';
2
+
3
+ /**
4
+ * @rezwanul7/settings/prisma: a Store over the app's own Prisma model delegate, such as `prisma.setting`. The package
5
+ * doesn't depend on `@prisma/client`; {@link PrismaSettingsDelegate} describes the methods the Store calls, and the
6
+ * delegate Prisma generates for the model below fits it.
7
+ *
8
+ * ```prisma
9
+ * model Setting {
10
+ * scope String
11
+ * ownerId String
12
+ * overrides Json
13
+ * version Int @default(1)
14
+ * createdAt DateTime @default(now())
15
+ * updatedAt DateTime @updatedAt
16
+ *
17
+ * @@id([scope, ownerId])
18
+ * @@map("settings")
19
+ * }
20
+ * ```
21
+ *
22
+ * @module prisma
23
+ */
24
+
25
+ /** A JSON value as Prisma accepts it for a `Json` field. */
26
+ type JsonInput = string | number | boolean | null | {
27
+ readonly [key: string]: JsonInput;
28
+ } | readonly JsonInput[];
29
+ /**
30
+ * The methods of a Prisma model delegate that {@link PrismaStore} calls, typed by their shape so the package needn't
31
+ * depend on `@prisma/client`. Pass the delegate Prisma generates for the Setting model, such as `prisma.setting`.
32
+ */
33
+ interface PrismaSettingsDelegate {
34
+ /**
35
+ * Reads one row by the `scope_ownerId` compound key.
36
+ *
37
+ * @param args - The compound key to find.
38
+ * @returns The row, or `null` when there is none.
39
+ */
40
+ findUnique(args: {
41
+ where: {
42
+ scope_ownerId: {
43
+ scope: string;
44
+ ownerId: string;
45
+ };
46
+ };
47
+ }): PromiseLike<{
48
+ /** The `overrides` column. */
49
+ overrides: unknown;
50
+ /** The `version` column. */
51
+ version: number;
52
+ } | null>;
53
+ /**
54
+ * Creates a row, rejecting with an error whose `code` is `P2002` when one exists for the key.
55
+ *
56
+ * @param args - The new row's key and Overrides; its version comes from the column default.
57
+ * @returns The created row.
58
+ */
59
+ create(args: {
60
+ data: {
61
+ scope: string;
62
+ ownerId: string;
63
+ overrides: {
64
+ readonly [key: string]: JsonInput;
65
+ };
66
+ };
67
+ }): PromiseLike<{
68
+ /** The `version` column, set by its default. */
69
+ version: number;
70
+ }>;
71
+ /**
72
+ * Updates the rows matching a filter.
73
+ *
74
+ * @param args - The row's key and the version it must be at, and the new Overrides with the version increment.
75
+ * @returns How many rows were updated.
76
+ */
77
+ updateMany(args: {
78
+ where: {
79
+ scope: string;
80
+ ownerId: string;
81
+ version: number;
82
+ };
83
+ data: {
84
+ overrides: {
85
+ readonly [key: string]: JsonInput;
86
+ };
87
+ version: {
88
+ increment: number;
89
+ };
90
+ };
91
+ }): PromiseLike<{
92
+ /** How many rows matched. */
93
+ count: number;
94
+ }>;
95
+ /**
96
+ * Deletes the rows matching a filter; matching none is not an error.
97
+ *
98
+ * @param args - The row's key.
99
+ * @returns How many rows were deleted.
100
+ */
101
+ deleteMany(args: {
102
+ where: {
103
+ scope: string;
104
+ ownerId: string;
105
+ };
106
+ }): PromiseLike<{
107
+ /** How many rows matched. */
108
+ count: number;
109
+ }>;
110
+ }
111
+ /**
112
+ * A Store that keeps each Owner's Overrides as one row of the app's Prisma model, guarded by its `version` column.
113
+ * An update only matches the row at the version the caller read, and a create that finds the row already there is a
114
+ * conflict, so the retries of the settings created by `createSettings` work against a real database.
115
+ *
116
+ * Known limitation: `delete` removes the row, and a row created after it starts again at the column default of `1`,
117
+ * so versions can repeat across a delete. A write that read version `n` of the old row before `reset()`, and stores
118
+ * after other writes have recreated the row and brought it back to version `n`, overwrites the new row: the Overrides
119
+ * written since the reset are lost and the old ones, with that write's change, come back. It takes a `reset()` and
120
+ * another write to the same Owner both landing while that write is running. Avoiding it would need the version to
121
+ * outlive the row, which the model doesn't keep, so call `reset()` when no other write to the Owner is expected, such
122
+ * as when deleting the Owner.
123
+ *
124
+ * @example
125
+ * import { PrismaStore } from "@rezwanul7/settings/prisma";
126
+ *
127
+ * const settings = createSettings({
128
+ * definitions: { user },
129
+ * store: new PrismaStore({ delegate: prisma.setting }),
130
+ * });
131
+ */
132
+ declare class PrismaStore implements SettingsStore {
133
+ #private;
134
+ /**
135
+ * @param options - Takes `delegate`: the app's Prisma model delegate, such as `prisma.setting`.
136
+ */
137
+ constructor(options: {
138
+ delegate: PrismaSettingsDelegate;
139
+ });
140
+ /** {@inheritDoc @rezwanul7/settings!SettingsStore.read} */
141
+ read(owner: Owner): Promise<StoredOverrides | null>;
142
+ /** {@inheritDoc @rezwanul7/settings!SettingsStore.write} */
143
+ write(owner: Owner, overrides: Record<string, unknown>, expectedVersion: number | null): Promise<number | null>;
144
+ /** {@inheritDoc @rezwanul7/settings!SettingsStore.delete} */
145
+ delete(owner: Owner): Promise<void>;
146
+ }
147
+
148
+ export { type PrismaSettingsDelegate, PrismaStore };
package/dist/prisma.js ADDED
@@ -0,0 +1,53 @@
1
+ // src/prisma.ts
2
+ var PrismaStore = class {
3
+ #delegate;
4
+ /**
5
+ * @param options - Takes `delegate`: the app's Prisma model delegate, such as `prisma.setting`.
6
+ */
7
+ constructor(options) {
8
+ this.#delegate = options.delegate;
9
+ }
10
+ /** {@inheritDoc @rezwanul7/settings!SettingsStore.read} */
11
+ async read(owner) {
12
+ const row = await this.#delegate.findUnique({
13
+ where: { scope_ownerId: key(owner) }
14
+ });
15
+ if (row === null) return null;
16
+ return {
17
+ overrides: row.overrides,
18
+ version: row.version
19
+ };
20
+ }
21
+ /** {@inheritDoc @rezwanul7/settings!SettingsStore.write} */
22
+ async write(owner, overrides, expectedVersion) {
23
+ const json = overrides;
24
+ if (expectedVersion === null) {
25
+ try {
26
+ const row = await this.#delegate.create({
27
+ data: { ...key(owner), overrides: json }
28
+ });
29
+ return row.version;
30
+ } catch (error) {
31
+ if (isUniqueConstraintError(error)) return null;
32
+ throw error;
33
+ }
34
+ }
35
+ const { count } = await this.#delegate.updateMany({
36
+ where: { ...key(owner), version: expectedVersion },
37
+ data: { overrides: json, version: { increment: 1 } }
38
+ });
39
+ return count === 0 ? null : expectedVersion + 1;
40
+ }
41
+ /** {@inheritDoc @rezwanul7/settings!SettingsStore.delete} */
42
+ async delete(owner) {
43
+ await this.#delegate.deleteMany({ where: key(owner) });
44
+ }
45
+ };
46
+ function key(owner) {
47
+ return { scope: owner.scope, ownerId: owner.id };
48
+ }
49
+ function isUniqueConstraintError(error) {
50
+ return typeof error === "object" && error !== null && "code" in error && error.code === "P2002";
51
+ }
52
+
53
+ export { PrismaStore };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * One Scope plus an id, such as user `1`: the thing a set of Overrides belongs to. The Owner of a single-owner Scope
3
+ * has the id `"default"`.
4
+ */
5
+ interface Owner {
6
+ /** The Scope name, such as `"user"` or `"global"`. */
7
+ readonly scope: string;
8
+ /** The Owner's id within its Scope. */
9
+ readonly id: string;
10
+ }
11
+ /** An Owner's Overrides as a Store keeps them, with the version a write must name to replace them. */
12
+ interface StoredOverrides {
13
+ /**
14
+ * Everything stored for the Owner, as one JSON object. Defaults are never part of it. Every value in it must be a
15
+ * JSON value: a string, finite number, boolean, `null`, array or plain object.
16
+ */
17
+ readonly overrides: Record<string, unknown>;
18
+ /**
19
+ * Increases on every successful write; a write that names an older version is a conflict. A Store should never
20
+ * give an Owner a version it had before, even after {@link SettingsStore.delete}, or a write that read the old
21
+ * document could overwrite the new one.
22
+ */
23
+ readonly version: number;
24
+ }
25
+ /**
26
+ * Where Overrides are kept, such as a database table or process memory. A Store keeps one document per Owner and
27
+ * guards writes with a version. It reports a conflict by returning `null` from {@link SettingsStore.write}; it never
28
+ * throws for a conflict and never retries, because retrying is the job of the settings created by `createSettings`.
29
+ *
30
+ * A Store keeps Overrides as JSON: what it reads back equals `JSON.parse(JSON.stringify(overrides))` of what was
31
+ * written, so a `Date` reads back as its ISO string and an `undefined` value is dropped. Overrides must therefore be
32
+ * JSON values; for a date, store its string and convert it where the setting is used.
33
+ */
34
+ interface SettingsStore {
35
+ /**
36
+ * Reads an Owner's Overrides.
37
+ *
38
+ * @param owner - The Owner to read.
39
+ * @returns The Overrides and their version, or `null` when nothing is stored for the Owner.
40
+ */
41
+ read(owner: Owner): Promise<StoredOverrides | null>;
42
+ /**
43
+ * Replaces an Owner's Overrides if they are still at `expectedVersion`.
44
+ *
45
+ * @param owner - The Owner to write.
46
+ * @param overrides - The Owner's complete new Overrides.
47
+ * @param expectedVersion - The version the caller read, or `null` to create the document for an Owner with nothing
48
+ * stored.
49
+ * @returns The new version, or `null` on a conflict: the stored version isn't `expectedVersion`, or a create found
50
+ * a document already there.
51
+ */
52
+ write(owner: Owner, overrides: Record<string, unknown>, expectedVersion: number | null): Promise<number | null>;
53
+ /**
54
+ * Deletes everything stored for an Owner. Deleting an Owner with nothing stored does nothing.
55
+ *
56
+ * @param owner - The Owner to delete.
57
+ */
58
+ delete(owner: Owner): Promise<void>;
59
+ }
60
+
61
+ export type { Owner as O, SettingsStore as S, StoredOverrides as a };
@@ -0,0 +1,61 @@
1
+ /**
2
+ * One Scope plus an id, such as user `1`: the thing a set of Overrides belongs to. The Owner of a single-owner Scope
3
+ * has the id `"default"`.
4
+ */
5
+ interface Owner {
6
+ /** The Scope name, such as `"user"` or `"global"`. */
7
+ readonly scope: string;
8
+ /** The Owner's id within its Scope. */
9
+ readonly id: string;
10
+ }
11
+ /** An Owner's Overrides as a Store keeps them, with the version a write must name to replace them. */
12
+ interface StoredOverrides {
13
+ /**
14
+ * Everything stored for the Owner, as one JSON object. Defaults are never part of it. Every value in it must be a
15
+ * JSON value: a string, finite number, boolean, `null`, array or plain object.
16
+ */
17
+ readonly overrides: Record<string, unknown>;
18
+ /**
19
+ * Increases on every successful write; a write that names an older version is a conflict. A Store should never
20
+ * give an Owner a version it had before, even after {@link SettingsStore.delete}, or a write that read the old
21
+ * document could overwrite the new one.
22
+ */
23
+ readonly version: number;
24
+ }
25
+ /**
26
+ * Where Overrides are kept, such as a database table or process memory. A Store keeps one document per Owner and
27
+ * guards writes with a version. It reports a conflict by returning `null` from {@link SettingsStore.write}; it never
28
+ * throws for a conflict and never retries, because retrying is the job of the settings created by `createSettings`.
29
+ *
30
+ * A Store keeps Overrides as JSON: what it reads back equals `JSON.parse(JSON.stringify(overrides))` of what was
31
+ * written, so a `Date` reads back as its ISO string and an `undefined` value is dropped. Overrides must therefore be
32
+ * JSON values; for a date, store its string and convert it where the setting is used.
33
+ */
34
+ interface SettingsStore {
35
+ /**
36
+ * Reads an Owner's Overrides.
37
+ *
38
+ * @param owner - The Owner to read.
39
+ * @returns The Overrides and their version, or `null` when nothing is stored for the Owner.
40
+ */
41
+ read(owner: Owner): Promise<StoredOverrides | null>;
42
+ /**
43
+ * Replaces an Owner's Overrides if they are still at `expectedVersion`.
44
+ *
45
+ * @param owner - The Owner to write.
46
+ * @param overrides - The Owner's complete new Overrides.
47
+ * @param expectedVersion - The version the caller read, or `null` to create the document for an Owner with nothing
48
+ * stored.
49
+ * @returns The new version, or `null` on a conflict: the stored version isn't `expectedVersion`, or a create found
50
+ * a document already there.
51
+ */
52
+ write(owner: Owner, overrides: Record<string, unknown>, expectedVersion: number | null): Promise<number | null>;
53
+ /**
54
+ * Deletes everything stored for an Owner. Deleting an Owner with nothing stored does nothing.
55
+ *
56
+ * @param owner - The Owner to delete.
57
+ */
58
+ delete(owner: Owner): Promise<void>;
59
+ }
60
+
61
+ export type { Owner as O, SettingsStore as S, StoredOverrides as a };
package/package.json CHANGED
@@ -1,6 +1,69 @@
1
1
  {
2
2
  "name": "@rezwanul7/settings",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/rezwanul7/turbopack-package-development.git",
8
+ "directory": "packages/settings"
9
+ },
10
+ "type": "module",
11
+ "main": "./dist/index.cjs",
12
+ "module": "./dist/index.js",
13
+ "types": "./dist/index.d.ts",
14
+ "sideEffects": false,
15
+ "engines": {
16
+ "node": ">=22"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "exports": {
25
+ ".": {
26
+ "import": {
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
29
+ },
30
+ "require": {
31
+ "types": "./dist/index.d.cts",
32
+ "default": "./dist/index.cjs"
33
+ }
34
+ },
35
+ "./prisma": {
36
+ "import": {
37
+ "types": "./dist/prisma.d.ts",
38
+ "default": "./dist/prisma.js"
39
+ },
40
+ "require": {
41
+ "types": "./dist/prisma.d.cts",
42
+ "default": "./dist/prisma.cjs"
43
+ }
44
+ }
45
+ },
46
+ "devDependencies": {
47
+ "@repo/eslint-config": "0.0.0",
48
+ "@repo/typescript-config": "0.0.0",
49
+ "@types/node": "^22.15.3",
50
+ "@vitest/coverage-v8": "4.0.18",
51
+ "esbuild": "0.27.3",
52
+ "eslint": "^9.39.1",
53
+ "tsup": "^8.5.1",
54
+ "typedoc-plugin-markdown": "4.14.0",
55
+ "vitest": "^4.0.18",
56
+ "zod": "^4.6.7"
57
+ },
58
+ "scripts": {
59
+ "dev": "tsup --watch",
60
+ "build": "tsup",
61
+ "check-types": "tsc --noEmit",
62
+ "check-docs": "typedoc --readme none --disableSources",
63
+ "check-package": "publint && attw --pack . --profile node16",
64
+ "lint": "eslint .",
65
+ "test": "vitest run",
66
+ "test:watch": "vitest",
67
+ "test:coverage": "vitest run --coverage"
68
+ }
6
69
  }