agrs-sequelize-sdk 1.4.39 → 1.4.41

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/models/Article.js CHANGED
@@ -31,6 +31,12 @@ module.exports = (sequelize, DataTypes) => {
31
31
  type: DataTypes.STRING(1024),
32
32
  allowNull: true,
33
33
  },
34
+ sourceCampaignId: {
35
+ type: DataTypes.STRING,
36
+ allowNull: true,
37
+ comment:
38
+ "FB campaign id of the campaign that originally created this article. Populated by Tarzo mirror on create-success so duplicate-as-reuse can find the source when this article is reused. Nullable — not all articles have a known source (legacy rows, articles whose source campaign was deleted).",
39
+ },
34
40
  feed: {
35
41
  type: DataTypes.STRING,
36
42
  allowNull: false,
@@ -177,6 +183,10 @@ module.exports = (sequelize, DataTypes) => {
177
183
  unique: true,
178
184
  fields: ['providerId', 'feed'],
179
185
  name: 'unique_provider_per_feed'
186
+ },
187
+ {
188
+ fields: ['sourceCampaignId'],
189
+ name: 'idx_article_source_campaign_id'
180
190
  }
181
191
  ]
182
192
  }
@@ -89,15 +89,33 @@ module.exports = (sequelize, DataTypes) => {
89
89
  {
90
90
  tableName: "PublicApiIdempotencyCache",
91
91
  indexes: [
92
- // Primary lookup: (user_id, key). UNIQUE so INSERT ... ON CONFLICT
93
- // DO NOTHING atomically claims the key for the first caller — the
94
- // "9 concurrent retries" burst case where only ONE request should
95
- // execute the underlying work.
96
- { unique: true, fields: ["user_id", "key"] },
92
+ // Primary lookup: (user_id, key). UNIQUE so Postgres resolves N
93
+ // concurrent claims atomically the loser hits a unique violation,
94
+ // Sequelize's findOrCreate catches it, re-finds the winner's row, and
95
+ // returns created=false. That is what makes exactly ONE of the "9
96
+ // concurrent retries" execute the work while the rest get 409.
97
+ //
98
+ // THIS INDEX IS THE ENTIRE FEATURE. Without it every concurrent INSERT
99
+ // succeeds and every retry re-runs the operation. Verified in prod on
100
+ // 2026-07-12: index absent -> a 9-way burst gave 5 executions; index
101
+ // present (local) -> exactly 1.
102
+ //
103
+ // Named explicitly so the migration that backfills it on already-created
104
+ // tables targets the same index. sequelize.sync() runs with alter:false
105
+ // in prod (server.js gates it on DB_COLUMN_TYPE_UPDATE), so it creates
106
+ // the table but does NOT add indexes to a table that already exists.
107
+ {
108
+ name: "PublicApiIdempotencyCache_user_id_key_uniq",
109
+ unique: true,
110
+ fields: ["user_id", "key"],
111
+ },
97
112
  // Sweep index — cron does `DELETE WHERE expires_at < NOW()`. Not
98
113
  // partial (would need to update after every insert); regular
99
114
  // btree is fine for the sweep pattern.
100
- { fields: ["expires_at"] },
115
+ {
116
+ name: "PublicApiIdempotencyCache_expires_at_idx",
117
+ fields: ["expires_at"],
118
+ },
101
119
  ],
102
120
  }
103
121
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agrs-sequelize-sdk",
3
- "version": "1.4.39",
3
+ "version": "1.4.41",
4
4
  "main": "index.js",
5
5
  "scripts": {
6
6
  "start": "node index.js",