@devvit/server 0.11.17-next-2025-05-29-a51058b05.0 → 0.11.17-next-2025-06-02-02cef9c33.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,54 @@
1
+ import { Devvit } from '@devvit/public-api';
2
+ import type { AppConfig, AppPermissionConfig, AppPostCreateConfig } from '@devvit/shared-types/schemas/config-file.v1.js';
3
+ /**
4
+ * A subset of AppConfig (see config-file.v1.json) available in V8-flavored
5
+ * LinkedBundles except for the deprecated Blocks callbacks. This config is used
6
+ * to generate classic compatible builds for clients via the Devvit CLI and
7
+ * configure the singleton at runtime for the compute bootstrap. The dynamic
8
+ * behavior is for old templates.
9
+ *
10
+ * @experimental
11
+ */
12
+ export type DynamicAppConfig = {
13
+ permissions: AppPermissionConfig;
14
+ post?: DynamicAppPostConfig | undefined;
15
+ };
16
+ /** @experimental */
17
+ export type DynamicAppPostConfig = {
18
+ client: {
19
+ entry: string;
20
+ /** @deprecated */
21
+ blocks?: {
22
+ /** Render post inline or use custom renderer. */
23
+ render?: 'inline' | {
24
+ mode: 'modal';
25
+ /**
26
+ * The text for the button that launches the app in a modal.
27
+ * @default 'Launch App'
28
+ */
29
+ label?: (() => string) | undefined;
30
+ } | Devvit.CustomPostComponent | undefined;
31
+ };
32
+ };
33
+ create: DynamicAppPostCreateConfig;
34
+ };
35
+ /** @experimental */
36
+ export type DynamicAppPostCreateConfig = AppPostCreateConfig & {
37
+ /** @deprecated */
38
+ blocks?: {
39
+ /**
40
+ * App loading screen. Evaluated at post creation time. Call
41
+ * `Post.setCustomPostPreview()` to change after creation.
42
+ * @default 'Loading…'
43
+ */
44
+ render?(): JSX.Element | undefined;
45
+ /**
46
+ * Dynamic post title.
47
+ * @default post.create.title
48
+ */
49
+ title?(): string | Promise<string> | undefined;
50
+ } | undefined;
51
+ };
52
+ /** @experimental */
53
+ export declare function defineConfig(config: Readonly<DynamicAppConfig | AppConfig>): void;
54
+ //# sourceMappingURL=define-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"define-config.d.ts","sourceRoot":"","sources":["../src/define-config.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAgB,MAAM,EAA0C,MAAM,oBAAoB,CAAC;AAClG,OAAO,KAAK,EACV,SAAS,EACT,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,gDAAgD,CAAC;AAExD;;;;;;;;GAQG;AACH,MAAM,MAAM,gBAAgB,GAAG;IAC7B,WAAW,EAAE,mBAAmB,CAAC;IACjC,IAAI,CAAC,EAAE,oBAAoB,GAAG,SAAS,CAAC;CACzC,CAAC;AAEF,oBAAoB;AACpB,MAAM,MAAM,oBAAoB,GAAG;IACjC,MAAM,EAAE;QACN,KAAK,EAAE,MAAM,CAAC;QACd,kBAAkB;QAClB,MAAM,CAAC,EAAE;YACP,iDAAiD;YACjD,MAAM,CAAC,EACH,QAAQ,GACR;gBACE,IAAI,EAAE,OAAO,CAAC;gBACd;;;mBAGG;gBACH,KAAK,CAAC,EAAE,CAAC,MAAM,MAAM,CAAC,GAAG,SAAS,CAAC;aACpC,GACD,MAAM,CAAC,mBAAmB,GAC1B,SAAS,CAAC;SACf,CAAC;KACH,CAAC;IACF,MAAM,EAAE,0BAA0B,CAAC;CACpC,CAAC;AAEF,oBAAoB;AACpB,MAAM,MAAM,0BAA0B,GAAG,mBAAmB,GAAG;IAC7D,kBAAkB;IAClB,MAAM,CAAC,EACH;QACE;;;;WAIG;QACH,MAAM,CAAC,IAAI,GAAG,CAAC,OAAO,GAAG,SAAS,CAAC;QACnC;;;WAGG;QACH,KAAK,CAAC,IAAI,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,SAAS,CAAC;KAChD,GACD,SAAS,CAAC;CACf,CAAC;AAEF,oBAAoB;AACpB,wBAAgB,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,gBAAgB,GAAG,SAAS,CAAC,GAAG,IAAI,CAGjF"}
@@ -0,0 +1,77 @@
1
+ import { Devvit, useWebView } from '@devvit/public-api';
2
+ /** @experimental */
3
+ export function defineConfig(config) {
4
+ configurePermissions(config.permissions);
5
+ if (config.post)
6
+ configurePost(config.post);
7
+ }
8
+ function configurePermissions(permissions) {
9
+ // to-do: remove. This is a relic of LinkedBundle generation.
10
+ Devvit.configure({
11
+ http: {
12
+ enabled: permissions.http.enabled,
13
+ requestedFetchDomains: permissions.http.allowedDomains,
14
+ },
15
+ media: permissions.media,
16
+ // to-do: payments permissions.
17
+ realtime: permissions.realtime,
18
+ redditAPI: permissions.reddit.enabled,
19
+ modLog: permissions.reddit.scope === 'moderator',
20
+ // to-do: configure userActions with API granularity.
21
+ userActions: !!permissions.reddit.asUser.length,
22
+ redis: permissions.redis,
23
+ });
24
+ }
25
+ function configurePost(post) {
26
+ let render;
27
+ if (post.client.blocks?.render === 'inline')
28
+ render = renderInline.bind(undefined, post.client.entry);
29
+ else if (typeof post.client.blocks?.render === 'function')
30
+ render = post.client.blocks.render;
31
+ else
32
+ render = renderModal.bind(undefined, post.client.entry, post.client.blocks?.render?.label);
33
+ Devvit.addCustomPostType({
34
+ name: post.create.title,
35
+ render,
36
+ height: post.create.height,
37
+ });
38
+ if (post.create.menu.enable)
39
+ Devvit.addMenuItem({
40
+ forUserType: post.create.menu.scope === 'moderator' ? 'moderator' : 'loggedOut',
41
+ label: post.create.menu.label,
42
+ location: 'subreddit',
43
+ async onPress(_ev, ctx) {
44
+ const newPost = await createPost(ctx, post.create);
45
+ ctx.ui.showToast({
46
+ text: `Created post "${newPost.title}" in ${ctx.subredditName}. Navigating to it now.`,
47
+ appearance: 'success',
48
+ });
49
+ ctx.ui.navigateTo(newPost);
50
+ },
51
+ });
52
+ if (post.create.onInstall)
53
+ Devvit.addTrigger({
54
+ event: 'AppInstall',
55
+ async onEvent(_ev, ctx) {
56
+ await createPost(ctx, post.create);
57
+ },
58
+ });
59
+ }
60
+ async function createPost(ctx, create) {
61
+ if (!ctx.subredditName)
62
+ throw Error('no sub name');
63
+ return await ctx.reddit.submitPost({
64
+ preview: create.blocks?.render?.() ?? Devvit.createElement("text", null, "Loading\u2026"),
65
+ subredditName: ctx.subredditName,
66
+ title: (await create.blocks?.title?.()) ?? create.title,
67
+ });
68
+ }
69
+ function renderInline(entry, _ctx) {
70
+ return Devvit.createElement("webview", { url: entry, width: "100%", height: "100%" });
71
+ }
72
+ function renderModal(entry, label, _ctx) {
73
+ const webView = useWebView({ url: entry, onMessage() { } });
74
+ return (Devvit.createElement("vstack", { alignment: "center middle", height: "100%" },
75
+ Devvit.createElement("button", { onPress: webView.mount }, label?.() ?? 'Launch App'),
76
+ ";"));
77
+ }
package/index.d.ts CHANGED
@@ -1,7 +1,6 @@
1
1
  export { getContext } from './context.js';
2
2
  export { createServer } from './create-server.js';
3
+ export { defineConfig } from './define-config.js';
3
4
  export { getServerPort } from './get-server-port.js';
4
5
  export { RequestContext, setRequestContext } from './request-context.js';
5
- export { defineConfig } from './webbit-post.js';
6
- export { webbitEnable } from './webbit-server.js';
7
6
  //# sourceMappingURL=index.d.ts.map
package/index.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC"}
package/index.js CHANGED
@@ -1,6 +1,5 @@
1
1
  export { getContext } from './context.js';
2
2
  export { createServer } from './create-server.js';
3
+ export { defineConfig } from './define-config.js';
3
4
  export { getServerPort } from './get-server-port.js';
4
5
  export { RequestContext, setRequestContext } from './request-context.js';
5
- export { defineConfig } from './webbit-post.js';
6
- export { webbitEnable } from './webbit-server.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devvit/server",
3
- "version": "0.11.17-next-2025-05-29-a51058b05.0",
3
+ "version": "0.11.17-next-2025-06-02-02cef9c33.0",
4
4
  "license": "BSD-3-Clause",
5
5
  "repository": {
6
6
  "type": "git",
@@ -23,13 +23,13 @@
23
23
  },
24
24
  "types": "./index.d.ts",
25
25
  "dependencies": {
26
- "@devvit/protos": "0.11.17-next-2025-05-29-a51058b05.0",
27
- "@devvit/public-api": "0.11.17-next-2025-05-29-a51058b05.0",
28
- "@devvit/shared-types": "0.11.17-next-2025-05-29-a51058b05.0"
26
+ "@devvit/protos": "0.11.17-next-2025-06-02-02cef9c33.0",
27
+ "@devvit/public-api": "0.11.17-next-2025-06-02-02cef9c33.0",
28
+ "@devvit/shared-types": "0.11.17-next-2025-06-02-02cef9c33.0"
29
29
  },
30
30
  "devDependencies": {
31
- "@devvit/repo-tools": "0.11.17-next-2025-05-29-a51058b05.0",
32
- "@devvit/tsconfig": "0.11.17-next-2025-05-29-a51058b05.0",
31
+ "@devvit/repo-tools": "0.11.17-next-2025-06-02-02cef9c33.0",
32
+ "@devvit/tsconfig": "0.11.17-next-2025-06-02-02cef9c33.0",
33
33
  "eslint": "9.11.1",
34
34
  "typescript": "5.8.3",
35
35
  "vitest": "1.6.1"
@@ -38,5 +38,5 @@
38
38
  "directory": "dist"
39
39
  },
40
40
  "source": "./src/index.ts",
41
- "gitHead": "9bf59270eb165569a0b2b8e3a11094c6d061abad"
41
+ "gitHead": "669392bbc9c49dbb068099d4e472d5b14215e598"
42
42
  }
package/webbit-post.d.ts DELETED
@@ -1,46 +0,0 @@
1
- import { type Context, type CustomPostType, type SubmitCustomPostOptions } from '@devvit/public-api';
2
- /** Configuration parameters for creating a webbit post type. */
3
- type WebbitPostParams = Omit<CustomPostType, 'render'> & Partial<Pick<CustomPostType, 'render'>> & {
4
- /** The path to the HTML entrypoint for the frontend */
5
- entry: string;
6
- menu?: WebbitMenuParams;
7
- /** If true, the webbit post will be rendered inline in the post body instead of a focus mode webview with a launch button. */
8
- inline?: boolean;
9
- /**
10
- * The text for the button that launches the webbit app in focus mode
11
- * @defaultValue 'Launch App'
12
- */
13
- launchButtonText?: string;
14
- };
15
- /** Optional parameters for the automatically created subreddit menu item for creating posts. */
16
- type WebbitMenuParams = Partial<Pick<SubmitCustomPostOptions, 'preview'>> & {
17
- /**
18
- * Whether the menu item is enabled
19
- * @defaultValue true
20
- */
21
- enable?: boolean;
22
- /**
23
- * The label for the menu item
24
- * @defaultValue '[${app name}] New Post'
25
- */
26
- label?: string;
27
- /** The title of the post to be created. Can be a static string or a function that returns a string. */
28
- postTitle?: string | ((context: Context) => Promise<string> | string);
29
- };
30
- /**
31
- * @experimental
32
- *
33
- * @param {WebbitPostParams }params Parameters for the webbit post type.
34
- * @example
35
- * ```ts
36
- * defineConfig({
37
- * name: 'Hello Webbit',
38
- * description: 'Custom webbit post',
39
- * height: 'tall',
40
- * entry: 'page.html',
41
- * });
42
- * ```
43
- */
44
- export declare function defineConfig(params: WebbitPostParams): void;
45
- export {};
46
- //# sourceMappingURL=webbit-post.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"webbit-post.d.ts","sourceRoot":"","sources":["../src/webbit-post.tsx"],"names":[],"mappings":"AAKA,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,cAAc,EAKnB,KAAK,uBAAuB,EAI7B,MAAM,oBAAoB,CAAC;AAM5B,gEAAgE;AAChE,KAAK,gBAAgB,GAAG,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,GACpD,OAAO,CAAC,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC,GAAG;IACxC,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,8HAA8H;IAC9H,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEJ,gGAAgG;AAChG,KAAK,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC,uBAAuB,EAAE,SAAS,CAAC,CAAC,GAAG;IAC1E;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,uGAAuG;IACvG,SAAS,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC;CACvE,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,gBAAgB,GAAG,IAAI,CA6B3D"}
package/webbit-post.js DELETED
@@ -1,124 +0,0 @@
1
- import { EffectType } from '@devvit/protos';
2
- import { WebbitServerDefinition, } from '@devvit/protos/types/devvit/actor/webbit/webbit.js';
3
- import { Devvit, useChannel, useState, useWebView, } from '@devvit/public-api';
4
- import { useEffectEmitter } from '@devvit/public-api/devvit/internals/blocks/handler/BlocksHandler.js';
5
- import { extendDevvitPrototype } from '@devvit/public-api/devvit/internals/helpers/extendDevvitPrototype.js';
6
- import { newWebbitServer } from './webbit-server.js';
7
- /**
8
- * @experimental
9
- *
10
- * @param {WebbitPostParams }params Parameters for the webbit post type.
11
- * @example
12
- * ```ts
13
- * defineConfig({
14
- * name: 'Hello Webbit',
15
- * description: 'Custom webbit post',
16
- * height: 'tall',
17
- * entry: 'page.html',
18
- * });
19
- * ```
20
- */
21
- export function defineConfig(params) {
22
- // add the webbit server to the Devvit prototype
23
- const server = newWebbitServer();
24
- extendDevvitPrototype('Request', server.Request);
25
- // enable Devvit features
26
- Devvit.configure({
27
- realtime: true,
28
- redditAPI: true,
29
- });
30
- Devvit.provide(WebbitServerDefinition);
31
- let render = (context) => params.inline ? defaultInlineRender(context, params) : defaultRender(params);
32
- if (params.render) {
33
- render = params.render;
34
- }
35
- // setup the custom post type
36
- Devvit.addCustomPostType({ ...params, render });
37
- // setup a menu item to create a webbit post
38
- if (params.menu?.enable === false) {
39
- // If the menu is disabled, we don't add a menu item.
40
- return;
41
- }
42
- Devvit.addMenuItem(defaultMenuItem(params));
43
- }
44
- function defaultRender(params) {
45
- const launchButtonText = params.launchButtonText ?? 'Launch App';
46
- const [subscriptions, setSubscriptions] = useState([]);
47
- const onMessage = messageHandler(setSubscriptions);
48
- const { mount, postMessage } = useWebView({
49
- // URL of your web view content
50
- url: params.entry,
51
- // Handle messages from web view
52
- onMessage,
53
- });
54
- messageRelay(subscriptions, postMessage);
55
- return (Devvit.createElement("vstack", { alignment: "center middle", height: "100%" },
56
- Devvit.createElement("button", { onPress: mount }, launchButtonText),
57
- ";"));
58
- }
59
- function defaultInlineRender(context, params) {
60
- const webViewId = 'webView';
61
- const [subscriptions, setSubscriptions] = useState([]);
62
- messageRelay(subscriptions, (msg) => {
63
- context.ui.webView.postMessage(webViewId, msg);
64
- });
65
- const onMessage = messageHandler(setSubscriptions);
66
- return (Devvit.createElement("webview", { id: webViewId, url: params.entry, width: "100%", height: "100%", onMessage: onMessage }));
67
- }
68
- function messageHandler(setSubscriptions) {
69
- const emitter = useEffectEmitter();
70
- return (message) => {
71
- // if is object, check type
72
- if (typeof message === 'object' &&
73
- message !== null &&
74
- 'type' in message &&
75
- message.type === 'effect') {
76
- const effect = message.data;
77
- const dedupeKey = message.key;
78
- // Needs to be handled specially because its a dynamic subscription set.
79
- if (effect.type === EffectType.EFFECT_REALTIME_SUB) {
80
- setSubscriptions(effect.realtimeSubscriptions?.subscriptionIds ?? []);
81
- }
82
- emitter.emitEffect(dedupeKey, effect);
83
- }
84
- };
85
- }
86
- function messageRelay(subscriptions, sender) {
87
- for (const sub of subscriptions) {
88
- const channel = useChannel({
89
- name: sub,
90
- onMessage: (msg) => {
91
- sender({
92
- type: 'realtime',
93
- channel: sub,
94
- msg,
95
- });
96
- },
97
- });
98
- channel.subscribe();
99
- }
100
- }
101
- function defaultMenuItem({ name, menu }) {
102
- return {
103
- forUserType: ['moderator'],
104
- label: menu?.label || `[${name}] New Post`,
105
- location: 'subreddit',
106
- async onPress(_ev, ctx) {
107
- const title = typeof menu?.postTitle === 'function'
108
- ? await menu?.postTitle(ctx)
109
- : menu?.postTitle || name;
110
- if (!ctx.subredditName)
111
- throw Error('invalid subreddit name');
112
- const post = await ctx.reddit.submitPost({
113
- preview: menu?.preview || Devvit.createElement("text", null, "Loading\u2026"),
114
- subredditName: ctx.subredditName,
115
- title,
116
- });
117
- await ctx.ui.showToast({
118
- text: `Created post "${title}" in ${ctx.subredditName}. Navigating to it now.`,
119
- appearance: 'success',
120
- });
121
- ctx.ui.navigateTo(post);
122
- },
123
- };
124
- }