@assembly-js/app-foundation 0.0.53 → 0.0.54
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/CHANGELOG.md +4 -0
- package/CLAUDE.md +3 -1
- package/dist/app-installs.d.ts +17 -0
- package/dist/app-installs.js +34 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +1 -0
- package/package.json +1 -1
- package/skills/assembly-native-resources/SKILL.md +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -14,6 +14,10 @@ A foundation update reads the sections between an app's version and the target:
|
|
|
14
14
|
from every bullet except `[guidance]`, agent work from the check scripts those `[action]`
|
|
15
15
|
bullets ship. Write `[fix]`, `[behavior]` and `[action]` bullets for the app's owner.
|
|
16
16
|
|
|
17
|
+
## 0.0.54
|
|
18
|
+
- [new] Add `isAppInstalled` and `getInstalledAppIds`, helpers an app can adopt to check which optional modules are installed in its workspace.
|
|
19
|
+
- [guidance] The build agent is taught to check that a module is installed before relying on features that depend on it.
|
|
20
|
+
|
|
17
21
|
## 0.0.53
|
|
18
22
|
- [action] A page or server action that calls a helper exported from a client-component file errors for every visitor who opens it; the build now reports the call, and the helper moves to a plain module both sides import.
|
|
19
23
|
- [guidance] Tell the build agent that a helper shared between a server-rendered page and a client component belongs in a plain module, not the client component's file.
|
package/CLAUDE.md
CHANGED
|
@@ -167,7 +167,9 @@ Before adding a feature, check whether Assembly already covers it. If it does, *
|
|
|
167
167
|
|
|
168
168
|
**Always available (every workspace):** Clients, companies, and custom fields — see "The data model" below. Use them; do not rebuild them. Notification infrastructure (`listNotifications`, `createNotification`, `deleteNotification`, `markNotificationRead`, `markNotificationUnread`) is also always available.
|
|
169
169
|
|
|
170
|
-
**Optional modules (may or may not be installed in this workspace):** Messages, Files, Contracts, Forms, Billing, Knowledge Base, Tasks, and Notes. Before suggesting one or
|
|
170
|
+
**Optional modules (may or may not be installed in this workspace):** Messages, Files, Contracts, Forms, Billing, Knowledge Base, Tasks, and Notes. Before suggesting one or building on one, confirm it is installed: gate the feature with `isAppInstalled(assembly, <appId>)` from `@assembly-js/app-foundation/server` and render an honest "<app> isn't installed in this workspace" state when it is false; the plan's module list is the build-time source of truth. If it is installed, use it through the SDK; do not rebuild it. If it is not installed, do not suggest it, do not plan the app around it, and do not ask the user to install it.
|
|
171
|
+
|
|
172
|
+
**Never ask the user to test a client-facing write (a form request, message, invoice, notification) on a real client.** Suggest a throwaway test client instead, and say before the first test that a form request can't be undone from the app — it can only be removed in the dashboard.
|
|
171
173
|
|
|
172
174
|
- **Messages** — Direct messaging between staff and clients. SDK groups: Messages, MessageChannels.
|
|
173
175
|
- **Files** — Secure file/folder sharing. *For app-private uploads (attachments, exports) use the storage helpers from `@assembly-js/app-foundation/server` (see the `assembly-storage` skill) instead — Files is only for files the user wants in the portal Files tab.* SDK groups: Files, FileChannels.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { AssemblyAPI } from '@assembly-js/node-sdk';
|
|
2
|
+
/**
|
|
3
|
+
* @deprecated Built-in modules are being replaced by generated apps, so this
|
|
4
|
+
* map will be removed. Prefer an appId you resolved from the workspace; use
|
|
5
|
+
* these only to gate a feature on a module that still ships built in.
|
|
6
|
+
*/
|
|
7
|
+
export declare const LEGACY_APP_IDS: {
|
|
8
|
+
readonly messages: "38323633-6465-6132-3730-383065323230";
|
|
9
|
+
readonly billing: "64346438-6461-6431-6565-366361383434";
|
|
10
|
+
readonly files: "33343966-3136-6166-6561-343739303432";
|
|
11
|
+
readonly contracts: "34633033-3365-6561-6332-386434316430";
|
|
12
|
+
readonly forms: "66666663-3861-6633-3830-323337613065";
|
|
13
|
+
readonly helpdesk: "30643961-3563-3361-3035-396231663435";
|
|
14
|
+
readonly tasks: "0e1dff29-1ce3-40ac-b58d-264097f6b319";
|
|
15
|
+
};
|
|
16
|
+
export declare function getInstalledAppIds(assembly: AssemblyAPI): Promise<Set<string>>;
|
|
17
|
+
export declare function isAppInstalled(assembly: AssemblyAPI, appId: string): Promise<boolean>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// Every node-sdk call for install lookups lives in getInstalledAppIds, so the
|
|
2
|
+
// node-sdk namespace migration is a one-line change here and never reaches
|
|
3
|
+
// generated apps.
|
|
4
|
+
/**
|
|
5
|
+
* @deprecated Built-in modules are being replaced by generated apps, so this
|
|
6
|
+
* map will be removed. Prefer an appId you resolved from the workspace; use
|
|
7
|
+
* these only to gate a feature on a module that still ships built in.
|
|
8
|
+
*/
|
|
9
|
+
export const LEGACY_APP_IDS = {
|
|
10
|
+
messages: '38323633-6465-6132-3730-383065323230',
|
|
11
|
+
billing: '64346438-6461-6431-6565-366361383434',
|
|
12
|
+
files: '33343966-3136-6166-6561-343739303432',
|
|
13
|
+
contracts: '34633033-3365-6561-6332-386434316430',
|
|
14
|
+
forms: '66666663-3861-6633-3830-323337613065',
|
|
15
|
+
helpdesk: '30643961-3563-3361-3035-396231663435',
|
|
16
|
+
tasks: '0e1dff29-1ce3-40ac-b58d-264097f6b319',
|
|
17
|
+
};
|
|
18
|
+
// A disabled install is dropped: it is present in the list but its surfaces and
|
|
19
|
+
// API behave as if the app were not there.
|
|
20
|
+
export async function getInstalledAppIds(assembly) {
|
|
21
|
+
try {
|
|
22
|
+
const installs = await assembly.listAppInstalls({});
|
|
23
|
+
return new Set(installs.filter((i) => !i.disabled && i.appId).map((i) => i.appId));
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
// Fails closed so a lookup error renders the "not installed" state instead
|
|
27
|
+
// of throwing into the page.
|
|
28
|
+
console.error('getInstalledAppIds: listAppInstalls failed', err);
|
|
29
|
+
return new Set();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export async function isAppInstalled(assembly, appId) {
|
|
33
|
+
return (await getInstalledAppIds(assembly)).has(appId);
|
|
34
|
+
}
|
package/dist/server.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export * from './assembly.js';
|
|
|
3
3
|
export * from './action-route.js';
|
|
4
4
|
export * from './auth.js';
|
|
5
5
|
export * from './client-access.js';
|
|
6
|
+
export * from './app-installs.js';
|
|
6
7
|
export * from './mint-bearer.js';
|
|
7
8
|
export * from './verify-bearer.js';
|
|
8
9
|
export * from './nango.js';
|
package/dist/server.js
CHANGED
|
@@ -7,6 +7,7 @@ export * from './assembly.js';
|
|
|
7
7
|
export * from './action-route.js';
|
|
8
8
|
export * from './auth.js';
|
|
9
9
|
export * from './client-access.js';
|
|
10
|
+
export * from './app-installs.js';
|
|
10
11
|
export * from './mint-bearer.js';
|
|
11
12
|
export * from './verify-bearer.js';
|
|
12
13
|
export * from './nango.js';
|
package/package.json
CHANGED
|
@@ -9,6 +9,10 @@ description: Use any time the app references a native Assembly message channel,
|
|
|
9
9
|
|
|
10
10
|
Discover instead. The pattern is always the same two steps: **list, then match on a stable attribute** the workspace owner controls (a title, a name), or resolve the resource from the current request's identity. Never a literal.
|
|
11
11
|
|
|
12
|
+
## Check the app is installed first
|
|
13
|
+
|
|
14
|
+
Gate any feature that depends on another app with `isAppInstalled(assembly, <appId>)` from `@assembly-js/app-foundation/server`, and show an honest "<app> isn't installed in this workspace" state when it is false. It works the same for core, marketplace and custom apps.
|
|
15
|
+
|
|
12
16
|
## Discovering a form
|
|
13
17
|
|
|
14
18
|
A form's display name is on `fields`, not on the form itself. Walk every page — matching by name against only the first page reports the form as missing once the workspace has more than a page of forms:
|
|
@@ -31,6 +35,8 @@ await assembly.requestFormResponse({
|
|
|
31
35
|
});
|
|
32
36
|
```
|
|
33
37
|
|
|
38
|
+
**Testing a form request:** never ask the user to send one to a real client. Suggest a throwaway test client, and say before the first test that a request can't be undone from the app — it can only be removed in the dashboard.
|
|
39
|
+
|
|
34
40
|
There is **no** method for a custom app to submit answers to a native form — you can request a response from a client and read `listFormResponses`, nothing more. Build your own form (see `assembly-forms`) if the app needs to collect the answers itself.
|
|
35
41
|
|
|
36
42
|
## Discovering a message channel
|