@proteinjs/db-settings 1.3.45 → 1.3.47

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 (48) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/generated/index.d.ts.map +1 -1
  3. package/dist/generated/index.js +3 -1
  4. package/dist/generated/index.js.map +1 -1
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/src/Settings.d.ts +10 -1
  10. package/dist/src/Settings.d.ts.map +1 -1
  11. package/dist/src/Settings.js +30 -9
  12. package/dist/src/Settings.js.map +1 -1
  13. package/dist/src/migrations/DeduplicateSettingRows.d.ts +34 -0
  14. package/dist/src/migrations/DeduplicateSettingRows.d.ts.map +1 -0
  15. package/dist/src/migrations/DeduplicateSettingRows.js +171 -0
  16. package/dist/src/migrations/DeduplicateSettingRows.js.map +1 -0
  17. package/dist/src/tables/SettingTable.d.ts +8 -0
  18. package/dist/src/tables/SettingTable.d.ts.map +1 -1
  19. package/dist/src/tables/SettingTable.js +10 -0
  20. package/dist/src/tables/SettingTable.js.map +1 -1
  21. package/dist/test/DeduplicateSettingRows.test.d.ts +3 -0
  22. package/dist/test/DeduplicateSettingRows.test.d.ts.map +1 -0
  23. package/dist/test/DeduplicateSettingRows.test.js +195 -0
  24. package/dist/test/DeduplicateSettingRows.test.js.map +1 -0
  25. package/dist/test/SettingsOneRowPerName.test.d.ts +3 -0
  26. package/dist/test/SettingsOneRowPerName.test.d.ts.map +1 -0
  27. package/dist/test/SettingsOneRowPerName.test.js +213 -0
  28. package/dist/test/SettingsOneRowPerName.test.js.map +1 -0
  29. package/dist/test/util/HeldFirstWriteDriver.d.ts +36 -0
  30. package/dist/test/util/HeldFirstWriteDriver.d.ts.map +1 -0
  31. package/dist/test/util/HeldFirstWriteDriver.js +130 -0
  32. package/dist/test/util/HeldFirstWriteDriver.js.map +1 -0
  33. package/dist/test/util/TestSession.d.ts +22 -0
  34. package/dist/test/util/TestSession.d.ts.map +1 -0
  35. package/dist/test/util/TestSession.js +49 -0
  36. package/dist/test/util/TestSession.js.map +1 -0
  37. package/generated/index.ts +3 -1
  38. package/index.ts +2 -0
  39. package/jest.config.js +2 -0
  40. package/package.json +9 -5
  41. package/src/Settings.ts +20 -5
  42. package/src/migrations/DeduplicateSettingRows.ts +78 -0
  43. package/src/tables/SettingTable.ts +11 -1
  44. package/test/DeduplicateSettingRows.test.ts +83 -0
  45. package/test/SettingsOneRowPerName.test.ts +104 -0
  46. package/test/setup.js +2 -0
  47. package/test/util/HeldFirstWriteDriver.ts +110 -0
  48. package/test/util/TestSession.ts +35 -0
package/CHANGELOG.md CHANGED
@@ -3,6 +3,26 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [1.3.47](https://github.com/brentbahry/db/compare/@proteinjs/db-settings@1.3.46...@proteinjs/db-settings@1.3.47) (2026-10-04)
7
+
8
+
9
+ ### Bug Fixes
10
+
11
+ * **settings:** one row per name within a scope — a unique index over (scope, name), set inserts then updates on the refusal, a pre-schema-sync dedupe ([8f9b8ef](https://github.com/brentbahry/db/commit/8f9b8efb5eb1dcb60f0c782fff8074b8a7dcd1e6))
12
+ * **settings:** set updates first — a rewrite of an existing name is never a refused insert the driver reports ([4317d08](https://github.com/brentbahry/db/commit/4317d08d6bd9c1d7b5465a1459e980d9b787ab06))
13
+
14
+
15
+
16
+
17
+
18
+ ## [1.3.46](https://github.com/brentbahry/db/compare/@proteinjs/db-settings@1.3.45...@proteinjs/db-settings@1.3.46) (2026-10-02)
19
+
20
+ **Note:** Version bump only for package @proteinjs/db-settings
21
+
22
+
23
+
24
+
25
+
6
26
  ## [1.3.45](https://github.com/brentbahry/db/compare/@proteinjs/db-settings@1.3.44...@proteinjs/db-settings@1.3.45) (2026-10-01)
7
27
 
8
28
  **Note:** Version bump only for package @proteinjs/db-settings
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../generated/index.ts"],"names":[],"mappings":"AAAA,oCAAoC;AAEpC,OAAO,eAAe,CAAC;AACvB,OAAO,mBAAmB,CAAC;AAC3B,OAAO,oBAAoB,CAAC;AAC5B,OAAO,iBAAiB,CAAC;AAyBzB,cAAc,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../generated/index.ts"],"names":[],"mappings":"AAAA,oCAAoC;AAEpC,OAAO,eAAe,CAAC;AACvB,OAAO,mBAAmB,CAAC;AAC3B,OAAO,oBAAoB,CAAC;AAC5B,OAAO,iBAAiB,CAAC;AA2BzB,cAAc,UAAU,CAAC"}
@@ -20,12 +20,14 @@ require("@proteinjs/logger");
20
20
  require("@proteinjs/service");
21
21
  require("@proteinjs/user");
22
22
  /** Generate Source Graph */
23
- var sourceGraph = "{\"options\":{\"directed\":true,\"multigraph\":false,\"compound\":false},\"nodes\":[{\"v\":\"@proteinjs/db-settings/Settings\",\"value\":{\"packageName\":\"@proteinjs/db-settings\",\"name\":\"Settings\",\"filePath\":\"src/Settings.ts\",\"qualifiedName\":\"@proteinjs/db-settings/Settings\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[{\"name\":\"logger\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\"},{\"name\":\"serviceMetadata\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"}],\"methods\":[{\"name\":\"get\",\"returnType\":null,\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"name\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"defaultValue\",\"type\":{\"packageName\":\"\",\"name\":\"T\",\"filePath\":null,\"qualifiedName\":\"/T\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"set\",\"returnType\":null,\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"name\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"value\",\"type\":{\"packageName\":\"\",\"name\":\"any\",\"filePath\":null,\"qualifiedName\":\"/any\",\"typeParameters\":null,\"directParents\":null}}]}],\"typeParameters\":[],\"directParentInterfaces\":[{\"packageName\":\"@proteinjs/db-settings\",\"name\":\"SettingsService\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db-settings/SettingsService\",\"properties\":[],\"methods\":[],\"typeParameters\":[],\"directParents\":[]}],\"directParentClasses\":[],\"sourceType\":2}},{\"v\":\"@proteinjs/db-settings/SettingsService\",\"value\":{\"packageName\":\"@proteinjs/db-settings\",\"name\":\"SettingsService\",\"filePath\":\"src/services/SettingsService.ts\",\"qualifiedName\":\"@proteinjs/db-settings/SettingsService\",\"properties\":[],\"methods\":[{\"name\":\"get\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<T>\",\"filePath\":null,\"qualifiedName\":\"/Promise<T>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":false,\"isOptional\":false,\"isAbstract\":true,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"name\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"defaultValue\",\"type\":{\"packageName\":\"\",\"name\":\"T\",\"filePath\":null,\"qualifiedName\":\"/T\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"set\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<void>\",\"filePath\":null,\"qualifiedName\":\"/Promise<void>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":false,\"isOptional\":false,\"isAbstract\":true,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"name\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"value\",\"type\":{\"packageName\":\"\",\"name\":\"any\",\"filePath\":null,\"qualifiedName\":\"/any\",\"typeParameters\":null,\"directParents\":null}}]}],\"typeParameters\":[],\"directParents\":[{\"packageName\":\"@proteinjs/service\",\"name\":\"Service\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/service/Service\",\"properties\":[],\"methods\":[],\"typeParameters\":[],\"directParents\":[]}],\"sourceType\":3}},{\"v\":\"@proteinjs/service/Service\"},{\"v\":\"@proteinjs/db-settings/Setting\",\"value\":{\"packageName\":\"@proteinjs/db-settings\",\"name\":\"Setting\",\"filePath\":\"src/tables/SettingTable.ts\",\"qualifiedName\":\"@proteinjs/db-settings/Setting\",\"properties\":[{\"name\":\"name\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"},{\"name\":\"value\",\"type\":{\"packageName\":\"\",\"name\":\"any\",\"filePath\":null,\"qualifiedName\":\"/any\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"}],\"methods\":[],\"typeParameters\":[],\"directParents\":[{\"packageName\":\"@proteinjs/user\",\"name\":\"ScopedRecord\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/user/ScopedRecord\",\"properties\":[],\"methods\":[],\"typeParameters\":[],\"directParents\":[]}],\"sourceType\":3}},{\"v\":\"@proteinjs/user/ScopedRecord\"},{\"v\":\"@proteinjs/db-settings/SettingTable\",\"value\":{\"packageName\":\"@proteinjs/db-settings\",\"name\":\"SettingTable\",\"filePath\":\"src/tables/SettingTable.ts\",\"qualifiedName\":\"@proteinjs/db-settings/SettingTable\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[{\"name\":\"name\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"},{\"name\":\"auth\",\"type\":{\"packageName\":\"\",\"name\":\"Table<Setting>['auth']\",\"filePath\":null,\"qualifiedName\":\"/Table<Setting>['auth']\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"},{\"name\":\"columns\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"}],\"methods\":[],\"typeParameters\":[],\"directParentInterfaces\":[],\"directParentClasses\":[{\"packageName\":\"@proteinjs/db\",\"name\":\"Table\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db/Table\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[],\"methods\":[],\"typeParameters\":[\"@proteinjs/db-settings/Setting\"],\"directParentInterfaces\":[],\"directParentClasses\":[]}],\"sourceType\":2}},{\"v\":\"@proteinjs/db/Table\"}],\"edges\":[{\"v\":\"@proteinjs/db-settings/Settings\",\"w\":\"@proteinjs/db-settings/SettingsService\",\"value\":\"implements interface\"},{\"v\":\"@proteinjs/db-settings/SettingsService\",\"w\":\"@proteinjs/service/Service\",\"value\":\"extends interface\"},{\"v\":\"@proteinjs/db-settings/Setting\",\"w\":\"@proteinjs/user/ScopedRecord\",\"value\":\"extends interface\"},{\"v\":\"@proteinjs/db-settings/SettingTable\",\"w\":\"@proteinjs/db/Table\",\"value\":\"extends class\"}]}";
23
+ var sourceGraph = "{\"options\":{\"directed\":true,\"multigraph\":false,\"compound\":false},\"nodes\":[{\"v\":\"@proteinjs/db-settings/Settings\",\"value\":{\"packageName\":\"@proteinjs/db-settings\",\"name\":\"Settings\",\"filePath\":\"src/Settings.ts\",\"qualifiedName\":\"@proteinjs/db-settings/Settings\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[{\"name\":\"serviceMetadata\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"}],\"methods\":[{\"name\":\"get\",\"returnType\":null,\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"name\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"defaultValue\",\"type\":{\"packageName\":\"\",\"name\":\"T\",\"filePath\":null,\"qualifiedName\":\"/T\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"set\",\"returnType\":null,\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"name\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"value\",\"type\":{\"packageName\":\"\",\"name\":\"any\",\"filePath\":null,\"qualifiedName\":\"/any\",\"typeParameters\":null,\"directParents\":null}}]}],\"typeParameters\":[],\"directParentInterfaces\":[{\"packageName\":\"@proteinjs/db-settings\",\"name\":\"SettingsService\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db-settings/SettingsService\",\"properties\":[],\"methods\":[],\"typeParameters\":[],\"directParents\":[]}],\"directParentClasses\":[],\"sourceType\":2}},{\"v\":\"@proteinjs/db-settings/SettingsService\",\"value\":{\"packageName\":\"@proteinjs/db-settings\",\"name\":\"SettingsService\",\"filePath\":\"src/services/SettingsService.ts\",\"qualifiedName\":\"@proteinjs/db-settings/SettingsService\",\"properties\":[],\"methods\":[{\"name\":\"get\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<T>\",\"filePath\":null,\"qualifiedName\":\"/Promise<T>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":false,\"isOptional\":false,\"isAbstract\":true,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"name\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"defaultValue\",\"type\":{\"packageName\":\"\",\"name\":\"T\",\"filePath\":null,\"qualifiedName\":\"/T\",\"typeParameters\":null,\"directParents\":null}}]},{\"name\":\"set\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<void>\",\"filePath\":null,\"qualifiedName\":\"/Promise<void>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":false,\"isOptional\":false,\"isAbstract\":true,\"isStatic\":false,\"visibility\":\"public\",\"parameters\":[{\"name\":\"name\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"value\",\"type\":{\"packageName\":\"\",\"name\":\"any\",\"filePath\":null,\"qualifiedName\":\"/any\",\"typeParameters\":null,\"directParents\":null}}]}],\"typeParameters\":[],\"directParents\":[{\"packageName\":\"@proteinjs/service\",\"name\":\"Service\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/service/Service\",\"properties\":[],\"methods\":[],\"typeParameters\":[],\"directParents\":[]}],\"sourceType\":3}},{\"v\":\"@proteinjs/db-settings/DeduplicateSettingRows\",\"value\":{\"packageName\":\"@proteinjs/db-settings\",\"name\":\"DeduplicateSettingRows\",\"filePath\":\"src/migrations/DeduplicateSettingRows.ts\",\"qualifiedName\":\"@proteinjs/db-settings/DeduplicateSettingRows\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[{\"name\":\"DELETE_BATCH\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":true,\"visibility\":\"private\"},{\"name\":\"table\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"},{\"name\":\"record\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"}],\"methods\":[{\"name\":\"surplusRowIds\",\"returnType\":{\"packageName\":\"\",\"name\":\"Promise<string[]>\",\"filePath\":null,\"qualifiedName\":\"/Promise<string[]>\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":true,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"private\",\"parameters\":[]},{\"name\":\"newestFirst\",\"returnType\":{\"packageName\":\"\",\"name\":\"number\",\"filePath\":null,\"qualifiedName\":\"/number\",\"typeParameters\":null,\"directParents\":null},\"isAsync\":false,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":true,\"visibility\":\"private\",\"parameters\":[{\"name\":\"a\",\"type\":{\"packageName\":\"@proteinjs/db-settings\",\"name\":\"Setting\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db-settings/Setting\",\"typeParameters\":null,\"directParents\":null}},{\"name\":\"b\",\"type\":{\"packageName\":\"@proteinjs/db-settings\",\"name\":\"Setting\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db-settings/Setting\",\"typeParameters\":null,\"directParents\":null}}]}],\"typeParameters\":[],\"directParentInterfaces\":[{\"packageName\":\"@proteinjs/db\",\"name\":\"SourceRecordLoader\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db/SourceRecordLoader\",\"properties\":[],\"methods\":[],\"typeParameters\":[\"@proteinjs/db/Migration\"],\"directParents\":[]}],\"directParentClasses\":[],\"sourceType\":2}},{\"v\":\"@proteinjs/db/SourceRecordLoader\"},{\"v\":\"@proteinjs/db-settings/Setting\",\"value\":{\"packageName\":\"@proteinjs/db-settings\",\"name\":\"Setting\",\"filePath\":\"src/tables/SettingTable.ts\",\"qualifiedName\":\"@proteinjs/db-settings/Setting\",\"properties\":[{\"name\":\"name\",\"type\":{\"packageName\":\"\",\"name\":\"string\",\"filePath\":null,\"qualifiedName\":\"/string\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"},{\"name\":\"value\",\"type\":{\"packageName\":\"\",\"name\":\"any\",\"filePath\":null,\"qualifiedName\":\"/any\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"}],\"methods\":[],\"typeParameters\":[],\"directParents\":[{\"packageName\":\"@proteinjs/user\",\"name\":\"ScopedRecord\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/user/ScopedRecord\",\"properties\":[],\"methods\":[],\"typeParameters\":[],\"directParents\":[]}],\"sourceType\":3}},{\"v\":\"@proteinjs/user/ScopedRecord\"},{\"v\":\"@proteinjs/db-settings/SettingTable\",\"value\":{\"packageName\":\"@proteinjs/db-settings\",\"name\":\"SettingTable\",\"filePath\":\"src/tables/SettingTable.ts\",\"qualifiedName\":\"@proteinjs/db-settings/SettingTable\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[{\"name\":\"name\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"},{\"name\":\"indexes\",\"type\":{\"packageName\":\"\",\"name\":\"Table<Setting>['indexes']\",\"filePath\":null,\"qualifiedName\":\"/Table<Setting>['indexes']\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"},{\"name\":\"auth\",\"type\":{\"packageName\":\"\",\"name\":\"Table<Setting>['auth']\",\"filePath\":null,\"qualifiedName\":\"/Table<Setting>['auth']\",\"typeParameters\":null,\"directParents\":null},\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"},{\"name\":\"columns\",\"type\":null,\"isOptional\":false,\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\"}],\"methods\":[],\"typeParameters\":[],\"directParentInterfaces\":[],\"directParentClasses\":[{\"packageName\":\"@proteinjs/db\",\"name\":\"Table\",\"filePath\":null,\"qualifiedName\":\"@proteinjs/db/Table\",\"isAbstract\":false,\"isStatic\":false,\"visibility\":\"public\",\"properties\":[],\"methods\":[],\"typeParameters\":[\"@proteinjs/db-settings/Setting\"],\"directParentInterfaces\":[],\"directParentClasses\":[]}],\"sourceType\":2}},{\"v\":\"@proteinjs/db/Table\"},{\"v\":\"@proteinjs/service/Service\"}],\"edges\":[{\"v\":\"@proteinjs/db-settings/Settings\",\"w\":\"@proteinjs/db-settings/SettingsService\",\"value\":\"implements interface\"},{\"v\":\"@proteinjs/db-settings/DeduplicateSettingRows\",\"w\":\"@proteinjs/db/SourceRecordLoader\",\"value\":\"implements interface\"},{\"v\":\"@proteinjs/db-settings/Setting\",\"w\":\"@proteinjs/user/ScopedRecord\",\"value\":\"extends interface\"},{\"v\":\"@proteinjs/db-settings/SettingTable\",\"w\":\"@proteinjs/db/Table\",\"value\":\"extends class\"},{\"v\":\"@proteinjs/db-settings/SettingsService\",\"w\":\"@proteinjs/service/Service\",\"value\":\"extends interface\"}]}";
24
24
  /** Generate Source Links */
25
25
  var Settings_1 = require("../src/Settings");
26
+ var DeduplicateSettingRows_1 = require("../src/migrations/DeduplicateSettingRows");
26
27
  var SettingTable_1 = require("../src/tables/SettingTable");
27
28
  var sourceLinks = {
28
29
  '@proteinjs/db-settings/Settings': Settings_1.Settings,
30
+ '@proteinjs/db-settings/DeduplicateSettingRows': DeduplicateSettingRows_1.DeduplicateSettingRows,
29
31
  '@proteinjs/db-settings/SettingTable': SettingTable_1.SettingTable,
30
32
  };
31
33
  /** Load Source Graph and Links */
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../generated/index.ts"],"names":[],"mappings":";AAAA,oCAAoC;;;;;;;;;;;;;;;;AAEpC,yBAAuB;AACvB,6BAA2B;AAC3B,8BAA4B;AAC5B,2BAAyB;AAGzB,4BAA4B;AAE5B,IAAM,WAAW,GAAG,ggNAAggN,CAAC;AAGrhN,4BAA4B;AAE5B,4CAA2C;AAC3C,2DAA0D;AAE1D,IAAM,WAAW,GAAG;IACnB,iCAAiC,EAAE,mBAAQ;IAC3C,qCAAqC,EAAE,2BAAY;CACnD,CAAC;AAGF,kCAAkC;AAElC,oDAAyD;AACzD,6BAAgB,CAAC,KAAK,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AAGjD,2CAAyB"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../generated/index.ts"],"names":[],"mappings":";AAAA,oCAAoC;;;;;;;;;;;;;;;;AAEpC,yBAAuB;AACvB,6BAA2B;AAC3B,8BAA4B;AAC5B,2BAAyB;AAGzB,4BAA4B;AAE5B,IAAM,WAAW,GAAG,s7RAAs7R,CAAC;AAG38R,4BAA4B;AAE5B,4CAA2C;AAC3C,mFAAkF;AAClF,2DAA0D;AAE1D,IAAM,WAAW,GAAG;IACnB,iCAAiC,EAAE,mBAAQ;IAC3C,+CAA+C,EAAE,+CAAsB;IACvE,qCAAqC,EAAE,2BAAY;CACnD,CAAC;AAGF,kCAAkC;AAElC,oDAAyD;AACzD,6BAAgB,CAAC,KAAK,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AAGjD,2CAAyB"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from './src/Settings';
2
2
  export * from './src/tables/tables';
3
+ export * from './src/migrations/DeduplicateSettingRows';
3
4
  export * from './src/services/SettingsService';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAE/B,cAAc,qBAAqB,CAAC;AAEpC,cAAc,gCAAgC,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAE/B,cAAc,qBAAqB,CAAC;AAEpC,cAAc,yCAAyC,CAAC;AAExD,cAAc,gCAAgC,CAAC"}
package/dist/index.js CHANGED
@@ -16,5 +16,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./src/Settings"), exports);
18
18
  __exportStar(require("./src/tables/tables"), exports);
19
+ __exportStar(require("./src/migrations/DeduplicateSettingRows"), exports);
19
20
  __exportStar(require("./src/services/SettingsService"), exports);
20
21
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iDAA+B;AAE/B,sDAAoC;AAEpC,iEAA+C"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,iDAA+B;AAE/B,sDAAoC;AAEpC,0EAAwD;AAExD,iEAA+C"}
@@ -1,7 +1,16 @@
1
1
  import { SettingsService } from './services/SettingsService';
2
2
  export declare const getSettings: () => Settings;
3
+ /**
4
+ * The caller's named settings: one row per name within their scope (`SettingTable`'s unique
5
+ * index), so `get` reads the one row. `set` is one write that cannot mint a second row: it
6
+ * updates the row; when there is none it inserts; and when the index refuses that insert because
7
+ * another writer landed the row in between (the typed `DuplicateKeyError` — the race's only path)
8
+ * it updates the one row — the last writer's value is what the row holds. The update comes first
9
+ * because a rewrite of an existing name is the common act: insert-first made every rewrite a
10
+ * refused statement the driver reports as a failure. Without the index, two concurrent writers
11
+ * each updated nothing and each inserted, two rows of one name.
12
+ */
3
13
  export declare class Settings implements SettingsService {
4
- private logger;
5
14
  serviceMetadata: {
6
15
  auth: {
7
16
  allUsers: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"Settings.d.ts","sourceRoot":"","sources":["../../src/Settings.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAsB,MAAM,4BAA4B,CAAC;AAIjF,eAAO,MAAM,WAAW,gBAA4F,CAAC;AAErH,qBAAa,QAAS,YAAW,eAAe;IAC9C,OAAO,CAAC,MAAM,CAA+C;IACtD,eAAe;;;;MAIpB;IAEI,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC;IAUrC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG;CAQnC"}
1
+ {"version":3,"file":"Settings.d.ts","sourceRoot":"","sources":["../../src/Settings.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAsB,MAAM,4BAA4B,CAAC;AAIjF,eAAO,MAAM,WAAW,gBAA4F,CAAC;AAErH;;;;;;;;;GASG;AACH,qBAAa,QAAS,YAAW,eAAe;IACvC,eAAe;;;;MAIpB;IAEI,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,CAAC;IAUrC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG;CAcnC"}
@@ -37,15 +37,24 @@ var __generator = (this && this.__generator) || function (thisArg, body) {
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.Settings = exports.getSettings = void 0;
40
- var logger_1 = require("@proteinjs/logger");
40
+ var db_1 = require("@proteinjs/db");
41
41
  var SettingsService_1 = require("./services/SettingsService");
42
42
  var tables_1 = require("./tables/tables");
43
43
  var user_1 = require("@proteinjs/user");
44
44
  var getSettings = function () { return (typeof self === 'undefined' ? new Settings() : (0, SettingsService_1.getSettingsService)()); };
45
45
  exports.getSettings = getSettings;
46
+ /**
47
+ * The caller's named settings: one row per name within their scope (`SettingTable`'s unique
48
+ * index), so `get` reads the one row. `set` is one write that cannot mint a second row: it
49
+ * updates the row; when there is none it inserts; and when the index refuses that insert because
50
+ * another writer landed the row in between (the typed `DuplicateKeyError` — the race's only path)
51
+ * it updates the one row — the last writer's value is what the row holds. The update comes first
52
+ * because a rewrite of an existing name is the common act: insert-first made every rewrite a
53
+ * refused statement the driver reports as a failure. Without the index, two concurrent writers
54
+ * each updated nothing and each inserted, two rows of one name.
55
+ */
46
56
  var Settings = /** @class */ (function () {
47
57
  function Settings() {
48
- this.logger = new logger_1.Logger({ name: this.constructor.name });
49
58
  this.serviceMetadata = {
50
59
  auth: {
51
60
  allUsers: true,
@@ -72,21 +81,33 @@ var Settings = /** @class */ (function () {
72
81
  };
73
82
  Settings.prototype.set = function (name, value) {
74
83
  return __awaiter(this, void 0, void 0, function () {
75
- var db, rowsUpdated;
84
+ var db, error_1;
76
85
  return __generator(this, function (_a) {
77
86
  switch (_a.label) {
78
87
  case 0:
79
88
  db = (0, user_1.getScopedDb)();
80
89
  return [4 /*yield*/, db.update(tables_1.tables.Setting, { value: value }, { name: name })];
81
90
  case 1:
82
- rowsUpdated = _a.sent();
83
- if (!(rowsUpdated == 0)) return [3 /*break*/, 3];
84
- this.logger.info({ message: "Creating new setting", obj: { name: name, value: value } });
85
- return [4 /*yield*/, db.insert(tables_1.tables.Setting, { name: name, value: value })];
91
+ if ((_a.sent()) > 0) {
92
+ return [2 /*return*/];
93
+ }
94
+ _a.label = 2;
86
95
  case 2:
96
+ _a.trys.push([2, 4, , 6]);
97
+ return [4 /*yield*/, db.insert(tables_1.tables.Setting, { name: name, value: value })];
98
+ case 3:
99
+ _a.sent();
100
+ return [3 /*break*/, 6];
101
+ case 4:
102
+ error_1 = _a.sent();
103
+ if (!(0, db_1.isDuplicateKeyError)(error_1)) {
104
+ throw error_1;
105
+ }
106
+ return [4 /*yield*/, db.update(tables_1.tables.Setting, { value: value }, { name: name })];
107
+ case 5:
87
108
  _a.sent();
88
- _a.label = 3;
89
- case 3: return [2 /*return*/];
109
+ return [3 /*break*/, 6];
110
+ case 6: return [2 /*return*/];
90
111
  }
91
112
  });
92
113
  });
@@ -1 +1 @@
1
- {"version":3,"file":"Settings.js","sourceRoot":"","sources":["../../src/Settings.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,4CAA2C;AAC3C,8DAAiF;AACjF,0CAAyC;AACzC,wCAA8C;AAEvC,IAAM,WAAW,GAAG,cAAM,OAAA,CAAC,OAAO,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAE,IAAA,oCAAkB,GAAe,CAAC,EAAnF,CAAmF,CAAC;AAAxG,QAAA,WAAW,eAA6F;AAErH;IAAA;QACU,WAAM,GAAG,IAAI,eAAM,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC;QACtD,oBAAe,GAAG;YACvB,IAAI,EAAE;gBACJ,QAAQ,EAAE,IAAI;aACf;SACF,CAAC;IAoBJ,CAAC;IAlBO,sBAAG,GAAT,UAAa,IAAY,EAAE,YAAgB;;;;;;wBACnC,EAAE,GAAG,IAAA,kBAAW,GAAE,CAAC;wBACT,qBAAM,EAAE,CAAC,GAAG,CAAC,eAAM,CAAC,OAAO,EAAE,EAAE,IAAI,MAAA,EAAE,CAAC,EAAA;;wBAAhD,OAAO,GAAG,SAAsC;wBACtD,IAAI,CAAC,OAAO,EAAE;4BACZ,sBAAO,YAAY,EAAC;yBACrB;wBAED,sBAAO,OAAO,CAAC,KAAK,EAAC;;;;KACtB;IAEK,sBAAG,GAAT,UAAU,IAAY,EAAE,KAAU;;;;;;wBAC1B,EAAE,GAAG,IAAA,kBAAW,GAAE,CAAC;wBACL,qBAAM,EAAE,CAAC,MAAM,CAAC,eAAM,CAAC,OAAO,EAAE,EAAE,KAAK,OAAA,EAAE,EAAE,EAAE,IAAI,MAAA,EAAE,CAAC,EAAA;;wBAAlE,WAAW,GAAG,SAAoD;6BACpE,CAAA,WAAW,IAAI,CAAC,CAAA,EAAhB,wBAAgB;wBAClB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,sBAAsB,EAAE,GAAG,EAAE,EAAE,IAAI,MAAA,EAAE,KAAK,OAAA,EAAE,EAAE,CAAC,CAAC;wBAC5E,qBAAM,EAAE,CAAC,MAAM,CAAC,eAAM,CAAC,OAAO,EAAE,EAAE,IAAI,MAAA,EAAE,KAAK,OAAA,EAAE,CAAC,EAAA;;wBAAhD,SAAgD,CAAC;;;;;;KAEpD;IACH,eAAC;AAAD,CAAC,AA1BD,IA0BC;AA1BY,4BAAQ"}
1
+ {"version":3,"file":"Settings.js","sourceRoot":"","sources":["../../src/Settings.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oCAAoD;AACpD,8DAAiF;AACjF,0CAAyC;AACzC,wCAA8C;AAEvC,IAAM,WAAW,GAAG,cAAM,OAAA,CAAC,OAAO,IAAI,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,QAAQ,EAAE,CAAC,CAAC,CAAE,IAAA,oCAAkB,GAAe,CAAC,EAAnF,CAAmF,CAAC;AAAxG,QAAA,WAAW,eAA6F;AAErH;;;;;;;;;GASG;AACH;IAAA;QACS,oBAAe,GAAG;YACvB,IAAI,EAAE;gBACJ,QAAQ,EAAE,IAAI;aACf;SACF,CAAC;IA0BJ,CAAC;IAxBO,sBAAG,GAAT,UAAa,IAAY,EAAE,YAAgB;;;;;;wBACnC,EAAE,GAAG,IAAA,kBAAW,GAAE,CAAC;wBACT,qBAAM,EAAE,CAAC,GAAG,CAAC,eAAM,CAAC,OAAO,EAAE,EAAE,IAAI,MAAA,EAAE,CAAC,EAAA;;wBAAhD,OAAO,GAAG,SAAsC;wBACtD,IAAI,CAAC,OAAO,EAAE;4BACZ,sBAAO,YAAY,EAAC;yBACrB;wBAED,sBAAO,OAAO,CAAC,KAAK,EAAC;;;;KACtB;IAEK,sBAAG,GAAT,UAAU,IAAY,EAAE,KAAU;;;;;;wBAC1B,EAAE,GAAG,IAAA,kBAAW,GAAE,CAAC;wBACpB,qBAAM,EAAE,CAAC,MAAM,CAAC,eAAM,CAAC,OAAO,EAAE,EAAE,KAAK,OAAA,EAAE,EAAE,EAAE,IAAI,MAAA,EAAE,CAAC,EAAA;;wBAAzD,IAAI,CAAC,SAAoD,CAAC,GAAG,CAAC,EAAE;4BAC9D,sBAAO;yBACR;;;;wBAEC,qBAAM,EAAE,CAAC,MAAM,CAAC,eAAM,CAAC,OAAO,EAAE,EAAE,IAAI,MAAA,EAAE,KAAK,OAAA,EAAE,CAAC,EAAA;;wBAAhD,SAAgD,CAAC;;;;wBAEjD,IAAI,CAAC,IAAA,wBAAmB,EAAC,OAAK,CAAC,EAAE;4BAC/B,MAAM,OAAK,CAAC;yBACb;wBACD,qBAAM,EAAE,CAAC,MAAM,CAAC,eAAM,CAAC,OAAO,EAAE,EAAE,KAAK,OAAA,EAAE,EAAE,EAAE,IAAI,MAAA,EAAE,CAAC,EAAA;;wBAApD,SAAoD,CAAC;;;;;;KAExD;IACH,eAAC;AAAD,CAAC,AA/BD,IA+BC;AA/BY,4BAAQ"}
@@ -0,0 +1,34 @@
1
+ import { Migration, SourceRecordLoader } from '@proteinjs/db';
2
+ /**
3
+ * The data step a database that predates the one-row-per-name invariant takes: before the unique
4
+ * index over (scope, name) lands (schema sync's preflight refuses the index over duplicate rows),
5
+ * keep ONE row of every (scope, name) — the newest, by `updated`, then `created`, then id — and
6
+ * delete the rest. The surplus rows are the ones an update-then-insert `set` created under
7
+ * concurrent writers; the newest holds the last value written, which is what the one row keeps.
8
+ *
9
+ * Pre-schema-sync (runs at `Db.init`, before the DDL), so by that contract: idempotent (a second
10
+ * run finds no duplicates), tolerant of concurrent duplicate runs (every actor computes the same
11
+ * survivors and deletes by id; a row already gone deletes nothing), and a no-op on a fresh
12
+ * database whose setting table does not exist yet.
13
+ */
14
+ export declare class DeduplicateSettingRows implements SourceRecordLoader<Migration> {
15
+ /** Spanner binds at most 950 parameters to one statement; a delete names fewer ids than that. */
16
+ private static readonly DELETE_BATCH;
17
+ table: import("@proteinjs/db").Table<Migration>;
18
+ record: {
19
+ id: string;
20
+ description: string;
21
+ preSchemaSync: boolean;
22
+ estimate: () => Promise<{
23
+ units: number;
24
+ unit: string;
25
+ }>;
26
+ run: () => Promise<{
27
+ deleted: number;
28
+ }>;
29
+ };
30
+ /** The ids of every row that is not the newest of its (scope, name) — none when the table does not exist. */
31
+ private surplusRowIds;
32
+ private static newestFirst;
33
+ }
34
+ //# sourceMappingURL=DeduplicateSettingRows.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeduplicateSettingRows.d.ts","sourceRoot":"","sources":["../../../src/migrations/DeduplicateSettingRows.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAuB,kBAAkB,EAAqC,MAAM,eAAe,CAAC;AAKtH;;;;;;;;;;;GAWG;AACH,qBAAa,sBAAuB,YAAW,kBAAkB,CAAC,SAAS,CAAC;IAC1E,iGAAiG;IACjG,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAAO;IAE3C,KAAK,2CAAsB;IAC3B,MAAM;;;;;;;;;;;MAoBJ;IAEF,6GAA6G;YAC/F,aAAa;IAyB3B,OAAO,CAAC,MAAM,CAAC,WAAW;CAO3B"}
@@ -0,0 +1,171 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
39
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
40
+ if (ar || !(i in from)) {
41
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
42
+ ar[i] = from[i];
43
+ }
44
+ }
45
+ return to.concat(ar || Array.prototype.slice.call(from));
46
+ };
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.DeduplicateSettingRows = void 0;
49
+ var db_1 = require("@proteinjs/db");
50
+ var logger_1 = require("@proteinjs/logger");
51
+ var tables_1 = require("../tables/tables");
52
+ /**
53
+ * The data step a database that predates the one-row-per-name invariant takes: before the unique
54
+ * index over (scope, name) lands (schema sync's preflight refuses the index over duplicate rows),
55
+ * keep ONE row of every (scope, name) — the newest, by `updated`, then `created`, then id — and
56
+ * delete the rest. The surplus rows are the ones an update-then-insert `set` created under
57
+ * concurrent writers; the newest holds the last value written, which is what the one row keeps.
58
+ *
59
+ * Pre-schema-sync (runs at `Db.init`, before the DDL), so by that contract: idempotent (a second
60
+ * run finds no duplicates), tolerant of concurrent duplicate runs (every actor computes the same
61
+ * survivors and deletes by id; a row already gone deletes nothing), and a no-op on a fresh
62
+ * database whose setting table does not exist yet.
63
+ */
64
+ var DeduplicateSettingRows = /** @class */ (function () {
65
+ function DeduplicateSettingRows() {
66
+ var _this = this;
67
+ this.table = db_1.tables.Migration;
68
+ this.record = {
69
+ id: 'a95d9f1d-3760-43d3-af75-15b0f7964eb6',
70
+ description: 'Keep one setting row per scope and name (the newest) ahead of the unique index over them',
71
+ preSchemaSync: true,
72
+ estimate: function () { return __awaiter(_this, void 0, void 0, function () {
73
+ var _a;
74
+ return __generator(this, function (_b) {
75
+ switch (_b.label) {
76
+ case 0:
77
+ _a = {};
78
+ return [4 /*yield*/, this.surplusRowIds()];
79
+ case 1: return [2 /*return*/, (_a.units = (_b.sent()).length, _a.unit = 'rows', _a)];
80
+ }
81
+ });
82
+ }); },
83
+ run: function () { return __awaiter(_this, void 0, void 0, function () {
84
+ var surplus, db, start, batch, qb;
85
+ return __generator(this, function (_a) {
86
+ switch (_a.label) {
87
+ case 0: return [4 /*yield*/, this.surplusRowIds()];
88
+ case 1:
89
+ surplus = _a.sent();
90
+ db = (0, db_1.getDbAsSystem)();
91
+ start = 0;
92
+ _a.label = 2;
93
+ case 2:
94
+ if (!(start < surplus.length)) return [3 /*break*/, 5];
95
+ batch = surplus.slice(start, start + DeduplicateSettingRows.DELETE_BATCH);
96
+ qb = new db_1.QueryBuilderFactory().getQueryBuilder(tables_1.tables.Setting);
97
+ qb.condition({ field: 'id', operator: 'IN', value: batch });
98
+ return [4 /*yield*/, db.delete(tables_1.tables.Setting, qb)];
99
+ case 3:
100
+ _a.sent();
101
+ _a.label = 4;
102
+ case 4:
103
+ start += DeduplicateSettingRows.DELETE_BATCH;
104
+ return [3 /*break*/, 2];
105
+ case 5:
106
+ new logger_1.Logger({ name: this.constructor.name }).info({
107
+ message: 'Deleted the surplus setting rows',
108
+ obj: { deleted: surplus.length },
109
+ });
110
+ return [2 /*return*/, { deleted: surplus.length }];
111
+ }
112
+ });
113
+ }); },
114
+ };
115
+ }
116
+ /** The ids of every row that is not the newest of its (scope, name) — none when the table does not exist. */
117
+ DeduplicateSettingRows.prototype.surplusRowIds = function () {
118
+ var _a, _b;
119
+ return __awaiter(this, void 0, void 0, function () {
120
+ var db, rowsByKey, _i, _c, row, key, surplus, _d, _e, key, rows;
121
+ return __generator(this, function (_f) {
122
+ switch (_f.label) {
123
+ case 0:
124
+ db = (0, db_1.getDbAsSystem)();
125
+ return [4 /*yield*/, db.tableExists(tables_1.tables.Setting)];
126
+ case 1:
127
+ if (!(_f.sent())) {
128
+ return [2 /*return*/, []];
129
+ }
130
+ rowsByKey = {};
131
+ _i = 0;
132
+ return [4 /*yield*/, db.query(tables_1.tables.Setting, {})];
133
+ case 2:
134
+ _c = _f.sent();
135
+ _f.label = 3;
136
+ case 3:
137
+ if (!(_i < _c.length)) return [3 /*break*/, 5];
138
+ row = _c[_i];
139
+ key = JSON.stringify([(_a = row.scope) !== null && _a !== void 0 ? _a : null, row.name]);
140
+ rowsByKey[key] = __spreadArray(__spreadArray([], ((_b = rowsByKey[key]) !== null && _b !== void 0 ? _b : []), true), [row], false);
141
+ _f.label = 4;
142
+ case 4:
143
+ _i++;
144
+ return [3 /*break*/, 3];
145
+ case 5:
146
+ surplus = [];
147
+ for (_d = 0, _e = Object.keys(rowsByKey); _d < _e.length; _d++) {
148
+ key = _e[_d];
149
+ rows = rowsByKey[key];
150
+ if (rows.length < 2) {
151
+ continue;
152
+ }
153
+ rows.sort(DeduplicateSettingRows.newestFirst);
154
+ surplus.push.apply(surplus, rows.slice(1).map(function (row) { return row.id; }));
155
+ }
156
+ return [2 /*return*/, surplus];
157
+ }
158
+ });
159
+ });
160
+ };
161
+ DeduplicateSettingRows.newestFirst = function (a, b) {
162
+ return (b.updated.valueOf() - a.updated.valueOf() ||
163
+ b.created.valueOf() - a.created.valueOf() ||
164
+ (b.id > a.id ? 1 : b.id < a.id ? -1 : 0));
165
+ };
166
+ /** Spanner binds at most 950 parameters to one statement; a delete names fewer ids than that. */
167
+ DeduplicateSettingRows.DELETE_BATCH = 500;
168
+ return DeduplicateSettingRows;
169
+ }());
170
+ exports.DeduplicateSettingRows = DeduplicateSettingRows;
171
+ //# sourceMappingURL=DeduplicateSettingRows.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeduplicateSettingRows.js","sourceRoot":"","sources":["../../../src/migrations/DeduplicateSettingRows.ts"],"names":[],"mappings":"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"}
@@ -4,8 +4,16 @@ export interface Setting extends ScopedRecord {
4
4
  name: string;
5
5
  value: any;
6
6
  }
7
+ /**
8
+ * One row per name within a scope, by declaration: the unique index over (scope, name) is what
9
+ * keeps two writers from creating two rows of one name — `Settings.set` inserts, the index refuses
10
+ * the second writer's insert, and that writer updates the one row. The same name in another scope
11
+ * is another person's setting, its own row. A database that predates the index reaches the
12
+ * invariant through `DeduplicateSettingRows`, which runs before the index is added.
13
+ */
7
14
  export declare class SettingTable extends Table<Setting> {
8
15
  name: string;
16
+ indexes: Table<Setting>['indexes'];
9
17
  auth: Table<Setting>['auth'];
10
18
  columns: {
11
19
  scope: import("@proteinjs/db").Column<string, any>;
@@ -1 +1 @@
1
- {"version":3,"file":"SettingTable.d.ts","sourceRoot":"","sources":["../../../src/tables/SettingTable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAA8B,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,YAAY,EAA2B,MAAM,iBAAiB,CAAC;AAExE,MAAM,WAAW,OAAQ,SAAQ,YAAY;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,GAAG,CAAC;CACZ;AAED,qBAAa,YAAa,SAAQ,KAAK,CAAC,OAAO,CAAC;IACvC,IAAI,SAAa;IACjB,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAOjC;IACK,OAAO;;;;;;;;;;MAGX;CACJ"}
1
+ {"version":3,"file":"SettingTable.d.ts","sourceRoot":"","sources":["../../../src/tables/SettingTable.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAA8B,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,YAAY,EAA8C,MAAM,iBAAiB,CAAC;AAE3F,MAAM,WAAW,OAAQ,SAAQ,YAAY;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,GAAG,CAAC;CACZ;AAED;;;;;;GAMG;AACH,qBAAa,YAAa,SAAQ,KAAK,CAAC,OAAO,CAAC;IACvC,IAAI,SAAa;IACjB,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,CAEvC;IACK,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAOjC;IACK,OAAO;;;;;;;;;;MAGX;CACJ"}
@@ -18,11 +18,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
18
18
  exports.SettingTable = void 0;
19
19
  var db_1 = require("@proteinjs/db");
20
20
  var user_1 = require("@proteinjs/user");
21
+ /**
22
+ * One row per name within a scope, by declaration: the unique index over (scope, name) is what
23
+ * keeps two writers from creating two rows of one name — `Settings.set` inserts, the index refuses
24
+ * the second writer's insert, and that writer updates the one row. The same name in another scope
25
+ * is another person's setting, its own row. A database that predates the index reaches the
26
+ * invariant through `DeduplicateSettingRows`, which runs before the index is added.
27
+ */
21
28
  var SettingTable = /** @class */ (function (_super) {
22
29
  __extends(SettingTable, _super);
23
30
  function SettingTable() {
24
31
  var _this = _super !== null && _super.apply(this, arguments) || this;
25
32
  _this.name = 'setting';
33
+ _this.indexes = [
34
+ (0, user_1.createScopedIndex)({ columns: ['name'], name: 'setting_scope_name_unique', unique: true }),
35
+ ];
26
36
  _this.auth = {
27
37
  db: {
28
38
  all: 'authenticated',
@@ -1 +1 @@
1
- {"version":3,"file":"SettingTable.js","sourceRoot":"","sources":["../../../src/tables/SettingTable.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAAA,oCAAkE;AAClE,wCAAwE;AAOxE;IAAkC,gCAAc;IAAhD;QAAA,qEAcC;QAbQ,UAAI,GAAG,SAAS,CAAC;QACjB,UAAI,GAA2B;YACpC,EAAE,EAAE;gBACF,GAAG,EAAE,eAAe;aACrB;YACD,OAAO,EAAE;gBACP,GAAG,EAAE,eAAe;aACrB;SACF,CAAC;QACK,aAAO,GAAG,IAAA,8BAAuB,EAAU;YAChD,IAAI,EAAE,IAAI,iBAAY,CAAC,MAAM,CAAC;YAC9B,KAAK,EAAE,IAAI,iBAAY,CAAC,OAAO,CAAC;SACjC,CAAC,CAAC;;IACL,CAAC;IAAD,mBAAC;AAAD,CAAC,AAdD,CAAkC,UAAK,GActC;AAdY,oCAAY"}
1
+ {"version":3,"file":"SettingTable.js","sourceRoot":"","sources":["../../../src/tables/SettingTable.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;AAAA,oCAAkE;AAClE,wCAA2F;AAO3F;;;;;;GAMG;AACH;IAAkC,gCAAc;IAAhD;QAAA,qEAiBC;QAhBQ,UAAI,GAAG,SAAS,CAAC;QACjB,aAAO,GAA8B;YAC1C,IAAA,wBAAiB,EAAU,EAAE,OAAO,EAAE,CAAC,MAAM,CAAC,EAAE,IAAI,EAAE,2BAA2B,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;SACnG,CAAC;QACK,UAAI,GAA2B;YACpC,EAAE,EAAE;gBACF,GAAG,EAAE,eAAe;aACrB;YACD,OAAO,EAAE;gBACP,GAAG,EAAE,eAAe;aACrB;SACF,CAAC;QACK,aAAO,GAAG,IAAA,8BAAuB,EAAU;YAChD,IAAI,EAAE,IAAI,iBAAY,CAAC,MAAM,CAAC;YAC9B,KAAK,EAAE,IAAI,iBAAY,CAAC,OAAO,CAAC;SACjC,CAAC,CAAC;;IACL,CAAC;IAAD,mBAAC;AAAD,CAAC,AAjBD,CAAkC,UAAK,GAiBtC;AAjBY,oCAAY"}
@@ -0,0 +1,3 @@
1
+ import '@proteinjs/db-transaction-context';
2
+ import '../generated/index';
3
+ //# sourceMappingURL=DeduplicateSettingRows.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeduplicateSettingRows.test.d.ts","sourceRoot":"","sources":["../../test/DeduplicateSettingRows.test.ts"],"names":[],"mappings":"AAOA,OAAO,mCAAmC,CAAC;AAC3C,OAAO,oBAAoB,CAAC"}