@devvit/build-pack 0.12.1-next-2025-09-03-16-45-14-78942cdf1.0 → 0.12.1-next-2025-09-03-18-30-49-c39f0866a.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.
@@ -1,5 +1,7 @@
1
+ import type { Metadata } from '@devvit/protos';
1
2
  import { Devvit, type FormKey } from '@devvit/public-api';
2
- import type { UiResponse } from '@devvit/shared';
3
+ import type { SettingsValidationResponse, UiResponse } from '@devvit/shared';
4
+ import type { JsonObject, PartialJsonObject } from '@devvit/shared-types/json.js';
3
5
  declare module '@devvit/public-api' {
4
6
  namespace Devvit {
5
7
  function _initForms(): void;
@@ -13,6 +15,21 @@ export declare const formKeyMap: {
13
15
  [formKey: string]: FormKey;
14
16
  };
15
17
  /** @internal */
16
- export declare function validateUiResponse(uiResponse: UiResponse): void;
18
+ export declare function abbreviate(str: string): string;
19
+ /** @internal */
20
+ export declare function assertUiResponse(endpoint: string, rsp: Readonly<PartialJsonObject>): asserts rsp is UiResponse;
21
+ /** @internal */
22
+ export declare function assertSettingsValidationResponse(rsp: Readonly<JsonObject>): asserts rsp is SettingsValidationResponse;
23
+ /**
24
+ * Post to endpoint and return user Node.js server response. All responses are
25
+ * expected to be empty or a JSON _object_.
26
+ *
27
+ * @throws Throws on `!Response.ok`.
28
+ * @throws Response body is nonempty and content-type is not JSON.
29
+ * @throws Response body is nonempty and unparsable.
30
+ * @throws Response body is nonempty and not a JSON object.
31
+ * @internal
32
+ */
33
+ export declare function fetchWebbit(endpoint: string, body: Readonly<PartialJsonObject>, meta: Readonly<Metadata>): Promise<JsonObject | undefined>;
17
34
  export default Devvit;
18
35
  //# sourceMappingURL=blocks.template.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"blocks.template.d.ts","sourceRoot":"","sources":["../../../src/esbuild/templatizer/blocks.template.tsx"],"names":[],"mappings":"AAGA,OAAO,EAEL,MAAM,EACN,KAAK,OAAO,EAOb,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAA8C,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAkB7F,OAAO,QAAQ,oBAAoB,CAAC;IAGlC,UAAU,MAAM,CAAC;QACf,SAAS,UAAU,IAAI,IAAI,CAAC;QAC5B,SAAS,SAAS,IAAI,IAAI,CAAC;QAC3B,SAAS,cAAc,IAAI,IAAI,CAAC;QAChC,SAAS,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;KAC7D;CACF;AAQD,oFAAoF;AACpF,eAAO,MAAM,UAAU,EAAE;IAAE,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAA;CAAO,CAAC;AAiI7D,gBAAgB;AAChB,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,UAAU,GAAG,IAAI,CA4D/D;AA4OD,eAAe,MAAM,CAAC"}
1
+ {"version":3,"file":"blocks.template.d.ts","sourceRoot":"","sources":["../../../src/esbuild/templatizer/blocks.template.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAGvD,OAAO,EAEL,MAAM,EACN,KAAK,OAAO,EAMb,MAAM,oBAAoB,CAAC;AAG5B,OAAO,KAAK,EAAE,0BAA0B,EAAkB,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAE7F,OAAO,KAAK,EAAE,UAAU,EAAa,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAgB7F,OAAO,QAAQ,oBAAoB,CAAC;IAGlC,UAAU,MAAM,CAAC;QACf,SAAS,UAAU,IAAI,IAAI,CAAC;QAC5B,SAAS,SAAS,IAAI,IAAI,CAAC;QAC3B,SAAS,cAAc,IAAI,IAAI,CAAC;QAChC,SAAS,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC;KAC7D;CACF;AAQD,oFAAoF;AACpF,eAAO,MAAM,UAAU,EAAE;IAAE,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAA;CAAO,CAAC;AAE7D,gBAAgB;AAChB,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C;AAuGD,gBAAgB;AAChB,wBAAgB,gBAAgB,CAC9B,QAAQ,EAAE,MAAM,EAChB,GAAG,EAAE,QAAQ,CAAC,iBAAiB,CAAC,GAC/B,OAAO,CAAC,GAAG,IAAI,UAAU,CAiE3B;AAED,gBAAgB;AAChB,wBAAgB,gCAAgC,CAC9C,GAAG,EAAE,QAAQ,CAAC,UAAU,CAAC,GACxB,OAAO,CAAC,GAAG,IAAI,0BAA0B,CAO3C;AA4CD;;;;;;;;;GASG;AACH,wBAAsB,WAAW,CAC/B,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,QAAQ,CAAC,iBAAiB,CAAC,EACjC,IAAI,EAAE,QAAQ,CAAC,QAAQ,CAAC,GACvB,OAAO,CAAC,UAAU,GAAG,SAAS,CAAC,CA0DjC;AAmJD,eAAe,MAAM,CAAC"}
@@ -14,6 +14,10 @@ import { backgroundUrl } from '@devvit/splash/utils/assets.js';
14
14
  const config2 = globalThis.__devvit__?.config;
15
15
  /** @internal [state] Map of devvit.json form keys to Devvit-singleton form keys. */
16
16
  export const formKeyMap = {};
17
+ /** @internal */
18
+ export function abbreviate(str) {
19
+ return str.length > 256 ? `${str.slice(0, 256)}…` : str;
20
+ }
17
21
  function configurePermissions(permissions) {
18
22
  // to-do: remove. This is a relic of LinkedBundle generation.
19
23
  Devvit.configure({
@@ -62,11 +66,12 @@ function configureMenuItems(menuItems) {
62
66
  const menuItem = {
63
67
  label: action.label,
64
68
  location: action.location,
65
- onPress: async (ev, ctx) => {
66
- const responseJson = await callWebbitEndpoint(action.endpoint, ev, ctx.metadata);
67
- const uiResponse = responseJson;
68
- validateUiResponse(uiResponse);
69
- await handleUiResponse(ctx, uiResponse);
69
+ async onPress(ev, ctx) {
70
+ const rsp = await fetchWebbit(action.endpoint, ev, ctx.metadata);
71
+ if (!rsp)
72
+ return;
73
+ assertUiResponse(action.endpoint, rsp);
74
+ handleUiResponse(ctx, rsp);
70
75
  },
71
76
  };
72
77
  // "user" type is blank in Devvit classic. So if it's present in
@@ -85,7 +90,69 @@ function configureMenuItems(menuItems) {
85
90
  function configureForms(forms) {
86
91
  Devvit._initForms();
87
92
  for (const [name, endpoint] of Object.entries(forms)) {
88
- formKeyMap[name] = Devvit.createForm({ fields: [] }, (ev, ctx) => handleFormResponse(endpoint, ev, ctx));
93
+ formKeyMap[name] = Devvit.createForm({ fields: [] }, async (ev, ctx) => {
94
+ const rsp = await fetchWebbit(endpoint, ev.values, ctx.metadata);
95
+ if (!rsp)
96
+ return;
97
+ assertUiResponse(endpoint, rsp);
98
+ handleUiResponse(ctx, rsp);
99
+ });
100
+ }
101
+ }
102
+ // TODO: expand this to include Form definitions.
103
+ /** @internal */
104
+ export function assertUiResponse(endpoint, rsp) {
105
+ const preamble = `Node.js server endpoint ${endpoint} returned the wrong UiResponse;`;
106
+ const keyset = {
107
+ navigateTo: undefined,
108
+ showToast: undefined,
109
+ showForm: undefined,
110
+ };
111
+ for (const k in rsp)
112
+ if (!(k in keyset))
113
+ throw Error(`${preamble} unknown key "${k}": ${abbreviate(JSON.stringify(rsp))}`);
114
+ if ('navigateTo' in rsp) {
115
+ if (rsp.navigateTo == null ||
116
+ Array.isArray(rsp.navigateTo) ||
117
+ (typeof rsp.navigateTo !== 'string' && typeof rsp.navigateTo !== 'object') ||
118
+ (typeof rsp.navigateTo === 'object' && typeof rsp.navigateTo.url !== 'string'))
119
+ throw Error(`${preamble} navigateTo must be a string or \`{"url": string}\`: ${abbreviate(JSON.stringify(rsp.navigateTo))}`);
120
+ // navigateTo must be a valid URL or a Post object. (this is validated
121
+ // client-side, so we don't need to validate it here).
122
+ }
123
+ if ('showToast' in rsp) {
124
+ if (rsp.showToast == null ||
125
+ Array.isArray(rsp.showToast) ||
126
+ (typeof rsp.showToast !== 'string' && typeof rsp.showToast !== 'object') ||
127
+ (typeof rsp.showToast === 'object' &&
128
+ (typeof rsp.showToast.text !== 'string' ||
129
+ (rsp.showToast.appearance !== 'neutral' && rsp.showToast.appearance !== 'success'))))
130
+ throw Error(`${preamble} showToast must be a string or \`{"text": string}\`: ${abbreviate(JSON.stringify(rsp.showToast))}`);
131
+ }
132
+ if ('showForm' in rsp) {
133
+ if (rsp.showForm == null ||
134
+ Array.isArray(rsp.showForm) ||
135
+ typeof rsp.showForm !== 'object' ||
136
+ typeof rsp.showForm.name !== 'string' ||
137
+ typeof rsp.showForm.form !== 'object' ||
138
+ ('data' in rsp.showForm && typeof rsp.showForm.data !== 'object'))
139
+ throw Error(`${preamble} showForm must be a ShowForm: ${abbreviate(JSON.stringify(rsp.showForm))}`);
140
+ if (!formKeyMap[rsp.showForm.name]) {
141
+ throw new Error(`${preamble} form with name "${rsp.showForm.name}" not found in devvit.json. Consider adding:\n\n "forms": {"${rsp.showForm.name}":"/internal/your/endpoint"}\n\n`);
142
+ }
143
+ }
144
+ // navigateTo and showForm are mutually exclusive.
145
+ if (rsp.navigateTo && rsp.showForm) {
146
+ throw new Error('navigateTo and showForm cannot be used together in UiResponse');
147
+ }
148
+ }
149
+ /** @internal */
150
+ export function assertSettingsValidationResponse(rsp) {
151
+ if (!('success' in rsp) || typeof rsp.success !== 'boolean') {
152
+ throw new Error('SettingsValidationResponse must have a boolean "success" field');
153
+ }
154
+ if ('error' in rsp && typeof rsp.error !== 'string') {
155
+ throw new Error('"error" field in SettingsValidationResponse must be a string');
89
156
  }
90
157
  }
91
158
  function configureTriggers(triggers) {
@@ -99,78 +166,12 @@ function configureTriggers(triggers) {
99
166
  // Convert the hydrated old Protobuf to JSON. Don't use
100
167
  // Protobuf.toJSON() which would omit default values.
101
168
  const body = JSON.parse(JSON.stringify(ev));
102
- // Cast to JsonObject since interfaces are open types.
103
- await callWebbitEndpoint(endpoint, body, ctx.metadata);
169
+ await fetchWebbit(endpoint, body, ctx.metadata);
170
+ // Don't care about response.
104
171
  },
105
172
  });
106
173
  }
107
174
  }
108
- async function handleFormResponse(endpoint, event, ctx) {
109
- const responseJson = await callWebbitEndpoint(endpoint, event.values, ctx.metadata);
110
- const uiResponse = responseJson;
111
- validateUiResponse(uiResponse);
112
- await handleUiResponse(ctx, uiResponse);
113
- }
114
- // TODO: expand this to fully validate the UiResponse format, including Form definitions,
115
- // and convert signature to validateUiResponse(uiResponse: JsonValue): uiResponse is UiResponse
116
- /** @internal */
117
- export function validateUiResponse(uiResponse) {
118
- // Validations:
119
- // (1) The only valid fields on uiResponse are showToast, navigateTo, and showForm.
120
- const validKeys = ['showToast', 'navigateTo', 'showForm'];
121
- const invalidKeys = Object.keys(uiResponse).filter((key) => !validKeys.includes(key));
122
- if (invalidKeys.length > 0) {
123
- throw new Error(`Invalid fields found in UiResponse: "${invalidKeys.join('", "')}". Valid fields are: "${validKeys.join('", "')}"`);
124
- }
125
- // (2) showForm must have a name that exists in the formKeyMap.
126
- if (uiResponse.showForm) {
127
- if (!uiResponse.showForm.name) {
128
- throw new Error('showForm must have a name');
129
- }
130
- if (!formKeyMap[uiResponse.showForm.name]) {
131
- throw new Error(`Form with name "${uiResponse.showForm.name}" not found in devvit.json. Consider adding:\n\n "forms": {"${uiResponse.showForm.name}":"/internal/your/endpoint"}\n\n`);
132
- }
133
- }
134
- // (3) showToast must be a string or an object with only fields [text, appearance]
135
- // and text is a mandatory string.
136
- if (uiResponse.showToast !== undefined) {
137
- if (typeof uiResponse.showToast === 'string') {
138
- // Valid case: showToast is a string
139
- }
140
- else if (typeof uiResponse.showToast === 'object' && uiResponse.showToast !== null) {
141
- // Check if it's an object with valid fields
142
- const toastKeys = Object.keys(uiResponse.showToast);
143
- const validToastKeys = ['text', 'appearance'];
144
- const invalidToastKeys = toastKeys.filter((key) => !validToastKeys.includes(key));
145
- if (invalidToastKeys.length > 0) {
146
- throw new Error(`Invalid fields found in showToast: "${invalidToastKeys.join('", "')}". Valid fields are: "${validToastKeys.join('", ')}"`);
147
- }
148
- if (typeof uiResponse.showToast.text !== 'string') {
149
- throw new Error('showToast.text is required and must be a string');
150
- }
151
- }
152
- else {
153
- throw new Error('showToast must be a string or an object with text and optional appearance fields');
154
- }
155
- }
156
- // (4) navigateTo must be a valid URL or a Post object. (this is validated client-side, so
157
- // we don't need to validate it here).
158
- // (5) navigateTo and showForm are mutually exclusive.
159
- if (uiResponse.navigateTo && uiResponse.showForm) {
160
- throw new Error('navigateTo and showForm cannot be used together in UiResponse');
161
- }
162
- }
163
- function validateSettingsValidationResponse(response) {
164
- if (typeof response !== 'object' || response === null) {
165
- throw new Error('SettingsValidationResponse must be an object');
166
- }
167
- if (!('success' in response) || typeof response.success !== 'boolean') {
168
- throw new Error('SettingsValidationResponse must have a boolean "success" field');
169
- }
170
- if ('error' in response && typeof response.error !== 'string') {
171
- throw new Error('"error" field in SettingsValidationResponse must be a string');
172
- }
173
- }
174
175
  /**
175
176
  * Handle a UiResponse from a Webbit handler (menu action or form handler).
176
177
  * This is used to create client-side UI effects in Reddit clients as responses
@@ -178,7 +179,7 @@ function validateSettingsValidationResponse(response) {
178
179
  *
179
180
  * If multiple effects are present in the UiResponse, they will all be applied.
180
181
  */
181
- async function handleUiResponse(ctx, uiResponse) {
182
+ function handleUiResponse(ctx, uiResponse) {
182
183
  if (uiResponse.showToast) {
183
184
  ctx.ui.showToast(uiResponse.showToast);
184
185
  }
@@ -189,49 +190,63 @@ async function handleUiResponse(ctx, uiResponse) {
189
190
  ctx.ui.showFormInternal(formKeyMap[uiResponse.showForm.name], uiResponse.showForm.data, uiResponse.showForm.form);
190
191
  }
191
192
  }
192
- async function callWebbitEndpoint(endpoint, body, metadata) {
193
+ /**
194
+ * Post to endpoint and return user Node.js server response. All responses are
195
+ * expected to be empty or a JSON _object_.
196
+ *
197
+ * @throws Throws on `!Response.ok`.
198
+ * @throws Response body is nonempty and content-type is not JSON.
199
+ * @throws Response body is nonempty and unparsable.
200
+ * @throws Response body is nonempty and not a JSON object.
201
+ * @internal
202
+ */
203
+ export async function fetchWebbit(endpoint, body, meta) {
193
204
  const url = new URL(endpoint, `http://webbit.local:${getServerPort()}/`);
194
205
  const headers = {};
195
- Object.entries(metadata).forEach(([key, metadata]) => {
196
- headers[key] = metadata.values[0];
197
- });
206
+ for (const [k, v] of Object.entries(meta))
207
+ headers[k] = v.values.join();
198
208
  headers['Content-Type'] = 'application/json';
199
209
  headers['Accept'] = 'application/json';
200
- const response = await fetch(url, {
201
- method: 'POST',
202
- body: JSON.stringify(body),
203
- headers,
204
- });
205
- if (!response.ok) {
206
- const bodyText = await response.text();
207
- // Try parsing the response as JSON / UiResponse. If valid, return it so callers can handle
208
- // it like effects. If not, fall down to error case.
209
- try {
210
- const jsonResponse = JSON.parse(bodyText);
211
- validateUiResponse(jsonResponse);
212
- return jsonResponse;
213
- }
214
- catch {
215
- let errorMessage = `Failed to POST ${endpoint}: ${response.statusText}, `;
216
- if (response.status === 404) {
217
- errorMessage += `ensure that you're handling this endpoint in your server code.`;
218
- }
219
- else {
220
- errorMessage += `body: ${bodyText.substring(0, 100)}`;
221
- }
222
- throw new Error(errorMessage);
223
- }
210
+ const preamble = `Failed to POST to Node.js server endpoint ${endpoint}; server responded with`;
211
+ let rsp;
212
+ try {
213
+ rsp = await fetch(url, {
214
+ body: JSON.stringify(body),
215
+ headers,
216
+ method: 'POST',
217
+ // to-do: redirect: 'manual'?
218
+ });
219
+ }
220
+ catch (err) {
221
+ throw `${preamble} error: ${err instanceof Error ? err.message : err}`;
224
222
  }
225
- const contentType = response.headers.get('Content-Type');
223
+ if (rsp.status === 404)
224
+ throw Error(`${preamble} HTTP status ${rsp.status}: ensure the server handles the \`${endpoint}\` endpoint`);
225
+ if (!rsp.ok)
226
+ throw Error(`${preamble} HTTP status ${rsp.status}: ${rsp.statusText}`);
227
+ if (!Number(rsp.headers.get('Content-Length')))
228
+ return;
229
+ const contentType = rsp.headers.get('Content-Type');
226
230
  if (!contentType || !contentType.includes('application/json')) {
227
- throw new Error(`Failed to POST ${endpoint}: expected response type 'application/json', received '${contentType}'`);
231
+ throw Error(`${preamble} Content-Type header "${contentType}" but only "application/json" is supported`);
232
+ }
233
+ let text;
234
+ try {
235
+ text = await rsp.text();
236
+ }
237
+ catch {
238
+ throw Error(`${preamble} an unreadable JSON body`);
228
239
  }
240
+ let json;
229
241
  try {
230
- return await response.json();
242
+ json = JSON.parse(text);
231
243
  }
232
- catch (error) {
233
- throw new Error(`Failed to POST ${endpoint}: ${error}`);
244
+ catch {
245
+ throw Error(`${preamble} an unparsable JSON body: ${abbreviate(text)}`);
234
246
  }
247
+ if (!json || typeof json !== 'object' || Array.isArray(json))
248
+ throw Error(`${preamble} an unrecognized JSON body instead of an object \`{}\`: ${abbreviate(text)}`);
249
+ return json;
235
250
  }
236
251
  function configureScheduler(schedulerConfig) {
237
252
  Devvit._initScheduler();
@@ -240,7 +255,8 @@ function configureScheduler(schedulerConfig) {
240
255
  Devvit.addSchedulerJob({
241
256
  name: name,
242
257
  onRun: async (event, context) => {
243
- await callWebbitEndpoint(task.endpoint, { name: event.name, data: event.data }, context.metadata);
258
+ await fetchWebbit(task.endpoint, { name: event.name, data: event.data }, context.metadata);
259
+ // Don't care about response.
244
260
  },
245
261
  });
246
262
  // Tasks with cron specified require a bit more work further down
@@ -326,12 +342,14 @@ function coerceSettingForClassic(setting, scope) {
326
342
  }
327
343
  if (setting.validationEndpoint) {
328
344
  classicSetting.onValidate = async function validateSettingsField(event, context) {
329
- const responseJson = await callWebbitEndpoint(setting.validationEndpoint, { value: event.value, isEditing: event.isEditing }, context.metadata);
330
- validateSettingsValidationResponse(responseJson);
331
- if (responseJson.success) {
345
+ const rsp = await fetchWebbit(setting.validationEndpoint, { value: event.value, isEditing: event.isEditing }, context.metadata);
346
+ if (!rsp)
347
+ return; // Assume success.
348
+ assertSettingsValidationResponse(rsp);
349
+ if (rsp.success) {
332
350
  return;
333
351
  }
334
- return responseJson.error;
352
+ return rsp.error;
335
353
  };
336
354
  }
337
355
  return classicSetting;
@@ -1 +1 @@
1
- {"version":3,"file":"templatizer.d.ts","sourceRoot":"","sources":["../../../src/esbuild/templatizer/templatizer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,gBAAgB;AAChB,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAUxF"}
1
+ {"version":3,"file":"templatizer.d.ts","sourceRoot":"","sources":["../../../src/esbuild/templatizer/templatizer.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,gBAAgB;AAChB,wBAAgB,UAAU,CAAC,IAAI,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAYxF"}
@@ -3,10 +3,12 @@ import path from 'node:path';
3
3
  export function templatize(root, blocksEntry) {
4
4
  const template = path.join(import.meta.dirname, 'blocks.template.js');
5
5
  const blocks = blocksEntry ? path.join(root, blocksEntry) : undefined;
6
- // Import user code second so that it has precedence.
6
+ // Import user code second so that it has precedence. Add `|| {}` to avoid
7
+ // esbuild warning on apps that don't have a default export.
7
8
  return `
8
9
  import Devvit from ${JSON.stringify(template.replaceAll('\\', '/'))};
9
- ${blocks ? `import ${JSON.stringify(blocks.replaceAll('\\', '/'))};` : ''}
10
- export default Devvit;
11
- `.trim();
10
+ ${blocks ? `import * as blocks from ${JSON.stringify(blocks.replaceAll('\\', '/'))};` : 'const blocks = undefined;'}
11
+ const Actor = (blocks || {}).default ?? Devvit;
12
+ export default Actor;
13
+ `.trim();
12
14
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devvit/build-pack",
3
- "version": "0.12.1-next-2025-09-03-16-45-14-78942cdf1.0",
3
+ "version": "0.12.1-next-2025-09-03-18-30-49-c39f0866a.0",
4
4
  "license": "BSD-3-Clause",
5
5
  "repository": {
6
6
  "type": "git",
@@ -31,9 +31,9 @@
31
31
  },
32
32
  "types": "./dist/index.d.ts",
33
33
  "dependencies": {
34
- "@devvit/protos": "0.12.1-next-2025-09-03-16-45-14-78942cdf1.0",
35
- "@devvit/shared-types": "0.12.1-next-2025-09-03-16-45-14-78942cdf1.0",
36
- "@devvit/splash": "0.12.1-next-2025-09-03-16-45-14-78942cdf1.0",
34
+ "@devvit/protos": "0.12.1-next-2025-09-03-18-30-49-c39f0866a.0",
35
+ "@devvit/shared-types": "0.12.1-next-2025-09-03-18-30-49-c39f0866a.0",
36
+ "@devvit/splash": "0.12.1-next-2025-09-03-18-30-49-c39f0866a.0",
37
37
  "esbuild": "0.23.0",
38
38
  "rxjs": "7.8.1",
39
39
  "tsv": "0.2.0",
@@ -44,16 +44,16 @@
44
44
  "@devvit/shared": "*"
45
45
  },
46
46
  "devDependencies": {
47
- "@devvit/public-api": "0.12.1-next-2025-09-03-16-45-14-78942cdf1.0",
48
- "@devvit/repo-tools": "0.12.1-next-2025-09-03-16-45-14-78942cdf1.0",
49
- "@devvit/scheduler": "0.12.1-next-2025-09-03-16-45-14-78942cdf1.0",
50
- "@devvit/server": "0.12.1-next-2025-09-03-16-45-14-78942cdf1.0",
51
- "@devvit/shared": "0.12.1-next-2025-09-03-16-45-14-78942cdf1.0",
52
- "@devvit/tsconfig": "0.12.1-next-2025-09-03-16-45-14-78942cdf1.0",
47
+ "@devvit/public-api": "0.12.1-next-2025-09-03-18-30-49-c39f0866a.0",
48
+ "@devvit/repo-tools": "0.12.1-next-2025-09-03-18-30-49-c39f0866a.0",
49
+ "@devvit/scheduler": "0.12.1-next-2025-09-03-18-30-49-c39f0866a.0",
50
+ "@devvit/server": "0.12.1-next-2025-09-03-18-30-49-c39f0866a.0",
51
+ "@devvit/shared": "0.12.1-next-2025-09-03-18-30-49-c39f0866a.0",
52
+ "@devvit/tsconfig": "0.12.1-next-2025-09-03-18-30-49-c39f0866a.0",
53
53
  "@types/tsv": "0.2.1",
54
54
  "eslint": "9.11.1",
55
55
  "vitest": "1.6.1"
56
56
  },
57
57
  "source": "./src/index.ts",
58
- "gitHead": "2c921f0c179aa4ed7ba1463d39620212e934fbce"
58
+ "gitHead": "75b507a70c64212d4e530f17bc55547ed0e8551c"
59
59
  }