@camstack/server 1.2.72 → 1.2.73

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.
@@ -98,15 +98,38 @@ function parseShareToken(data) {
98
98
  class ShareTokenService {
99
99
  getStore;
100
100
  logger;
101
+ /**
102
+ * Declaration is one-time per process; reset only if the backend itself
103
+ * is swapped (it never is at runtime — the getter is lazy solely because
104
+ * the sqlite builtin registers after this service is constructed).
105
+ */
106
+ collectionDeclared = false;
101
107
  constructor(getStore, logger = null) {
102
108
  this.getStore = getStore;
103
109
  this.logger = logger;
104
110
  }
105
- store() {
111
+ /**
112
+ * Resolve the backend AND ensure `share_view_tokens` is declared before
113
+ * the first operation. The structured settings backend fail-closes on
114
+ * undeclared collections; this KV shape (`id` PK + `data` TEXT) is
115
+ * byte-identical to what the legacy on-demand path created, so existing
116
+ * rows keep working with no migration.
117
+ */
118
+ async store() {
106
119
  const store = this.getStore();
107
120
  if (!store) {
108
121
  throw new Error('Share tokens unavailable — settings backend not ready');
109
122
  }
123
+ if (!this.collectionDeclared) {
124
+ await store.declareCollection({
125
+ collection: SHARE_TOKENS_COLLECTION,
126
+ columns: [
127
+ { name: 'id', type: 'TEXT', primaryKey: true, notNull: true },
128
+ { name: 'data', type: 'TEXT', notNull: true },
129
+ ],
130
+ });
131
+ this.collectionDeclared = true;
132
+ }
110
133
  return store;
111
134
  }
112
135
  /**
@@ -137,7 +160,7 @@ class ShareTokenService {
137
160
  createdAt: now,
138
161
  expiresAt: ttlSec === 'never' ? null : now + ttlSec * 1000,
139
162
  };
140
- await this.store().insert({
163
+ await (await this.store()).insert({
141
164
  collection: SHARE_TOKENS_COLLECTION,
142
165
  record: { id: record.id, data: { ...record } },
143
166
  });
@@ -161,7 +184,7 @@ class ShareTokenService {
161
184
  if (!rawToken.startsWith(exports.SHARE_TOKEN_PREFIX))
162
185
  return null;
163
186
  const tokenHash = crypto.createHash('sha256').update(rawToken).digest('hex');
164
- const results = await this.store().query({
187
+ const results = await (await this.store()).query({
165
188
  collection: SHARE_TOKENS_COLLECTION,
166
189
  filter: { where: { tokenHash } },
167
190
  });
@@ -186,7 +209,7 @@ class ShareTokenService {
186
209
  * permission mismatch so the router surfaces a FORBIDDEN.
187
210
  */
188
211
  async revoke(input) {
189
- const results = await this.store().query({
212
+ const results = await (await this.store()).query({
190
213
  collection: SHARE_TOKENS_COLLECTION,
191
214
  filter: { where: { id: input.id } },
192
215
  });
@@ -196,13 +219,13 @@ class ShareTokenService {
196
219
  const record = parseShareToken(first.data);
197
220
  if (!record) {
198
221
  // Corrupt record — delete it regardless (it can never validate).
199
- await this.store().delete({ collection: SHARE_TOKENS_COLLECTION, key: input.id });
222
+ await (await this.store()).delete({ collection: SHARE_TOKENS_COLLECTION, key: input.id });
200
223
  return true;
201
224
  }
202
225
  if (!input.callerIsAdmin && record.userId !== input.callerUserId) {
203
226
  throw new Error('Only the token owner or an admin can revoke a share token');
204
227
  }
205
- await this.store().delete({ collection: SHARE_TOKENS_COLLECTION, key: input.id });
228
+ await (await this.store()).delete({ collection: SHARE_TOKENS_COLLECTION, key: input.id });
206
229
  this.logger?.info('Share token revoked', {
207
230
  meta: { id: record.id, byUserId: input.callerUserId },
208
231
  });
@@ -210,7 +233,7 @@ class ShareTokenService {
210
233
  }
211
234
  /** Every share token minted by `userId`, expired ones included. */
212
235
  async listForUser(userId) {
213
- const results = await this.store().query({
236
+ const results = await (await this.store()).query({
214
237
  collection: SHARE_TOKENS_COLLECTION,
215
238
  filter: { where: { userId } },
216
239
  });
@@ -218,14 +241,14 @@ class ShareTokenService {
218
241
  }
219
242
  /** All share tokens (admin listing). */
220
243
  async listAll() {
221
- const results = await this.store().query({
244
+ const results = await (await this.store()).query({
222
245
  collection: SHARE_TOKENS_COLLECTION,
223
246
  filter: {},
224
247
  });
225
248
  return results.map((r) => parseShareToken(r.data)).filter((r) => r !== null);
226
249
  }
227
250
  async touchLastUsed(record) {
228
- await this.store().update({
251
+ await (await this.store()).update({
229
252
  collection: SHARE_TOKENS_COLLECTION,
230
253
  id: record.id,
231
254
  data: { ...record, lastUsedAt: Date.now() },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/server",
3
- "version": "1.2.72",
3
+ "version": "1.2.73",
4
4
  "private": false,
5
5
  "files": [
6
6
  "dist",
@@ -33,19 +33,19 @@
33
33
  ]
34
34
  },
35
35
  "dependencies": {
36
- "@camstack/addon-admin-ui": "1.2.33",
36
+ "@camstack/addon-admin-ui": "1.2.34",
37
37
  "@camstack/addon-agent-ui": "1.2.10",
38
38
  "@camstack/addon-auth": "1.2.11",
39
39
  "@camstack/addon-decoder-nodeav": "1.2.9",
40
40
  "@camstack/addon-notifiers": "1.2.13",
41
- "@camstack/addon-pipeline": "1.2.44",
42
- "@camstack/addon-pipeline-orchestrator": "1.2.27",
43
- "@camstack/addon-post-analysis": "1.2.49",
41
+ "@camstack/addon-pipeline": "1.2.45",
42
+ "@camstack/addon-pipeline-orchestrator": "1.2.28",
43
+ "@camstack/addon-post-analysis": "1.2.50",
44
44
  "@camstack/sdk": "1.2.10",
45
45
  "@camstack/shm-ring": "1.1.9",
46
- "@camstack/system": "1.2.59",
47
- "@camstack/types": "1.2.43",
48
- "@camstack/ui-library": "1.2.31",
46
+ "@camstack/system": "1.2.60",
47
+ "@camstack/types": "1.2.44",
48
+ "@camstack/ui-library": "1.2.32",
49
49
  "@fastify/compress": "^9.0.0",
50
50
  "@fastify/cookie": "^11.0.2",
51
51
  "@fastify/cors": "^11.2.0",