@synopackageland/sdk 0.9.9 → 0.10.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.
Files changed (43) hide show
  1. package/README.md +121 -0
  2. package/dist/i18n/bridge/de.json +6 -0
  3. package/dist/i18n/bridge/en.json +6 -0
  4. package/dist/i18n/bridge/es.json +6 -0
  5. package/dist/i18n/bridge/fr.json +6 -0
  6. package/dist/i18n/bridge/it.json +6 -0
  7. package/dist/i18n/bridge/ja.json +6 -0
  8. package/dist/i18n/bridge/ko.json +6 -0
  9. package/dist/i18n/bridge/zh-CN.json +6 -0
  10. package/dist/i18n/bridge/zh-TW.json +6 -0
  11. package/dist/i18n/shell/de.json +5 -1
  12. package/dist/i18n/shell/en.json +5 -1
  13. package/dist/i18n/shell/es.json +5 -1
  14. package/dist/i18n/shell/fr.json +5 -1
  15. package/dist/i18n/shell/it.json +5 -1
  16. package/dist/i18n/shell/ja.json +5 -1
  17. package/dist/i18n/shell/ko.json +5 -1
  18. package/dist/i18n/shell/zh-CN.json +5 -1
  19. package/dist/i18n/shell/zh-TW.json +5 -1
  20. package/dist/index.js +2492 -2338
  21. package/dist/src/bridge/i18n.d.ts +5 -0
  22. package/dist/src/context.d.ts +23 -2
  23. package/dist/src/delegate/root.d.ts +40 -0
  24. package/dist/src/index.d.ts +5 -2
  25. package/dist/src/protocol/v1.d.ts +49 -4
  26. package/dist/src/shell/AppShell.vue.d.ts +31 -10
  27. package/dist/src/shell/app-pages.d.ts +37 -0
  28. package/dist/src/shell/i18n.d.ts +4 -0
  29. package/dist/src/shell/pages.d.ts +7 -2
  30. package/dist/src/shell/slot-content.d.ts +2 -0
  31. package/dist/src/standalone.d.ts +5 -0
  32. package/dist/src/vue.d.ts +1 -0
  33. package/dist/style.css +1 -1
  34. package/dist/vue.js +1639 -1510
  35. package/package.json +4 -4
  36. package/spec/protocol/v1/README.md +154 -3
  37. package/spec/protocol/v1/errors.json +36 -4
  38. package/spec/protocol/v1/schema/root.jobs.get.json +19 -0
  39. package/spec/protocol/v1/schema/root.jobs.list.json +11 -0
  40. package/spec/protocol/v1/schema/root.jobs.stop.json +19 -0
  41. package/spec/protocol/v1/schema/root.run.json +6 -2
  42. package/spec/protocol/v1/schema/root.start.json +23 -0
  43. package/spec/protocol/v1/vectors/files.jsonl +11 -0
package/README.md CHANGED
@@ -34,6 +34,117 @@ and passes it to `PortalSettings`. `call` reaches the package backend's
34
34
  Its `messages` are `portalMessages(locale)`, the panel text from
35
35
  `i18n/portal/<locale>.json` (`portalCatalogs`). It has no plain `mountPanel`.
36
36
 
37
+ ## Full page
38
+
39
+ A Portal or standalone launch gives the App the whole page; the DSM window
40
+ passes `ctx.fullPage`, `ctx.displayName` and `ctx.appIcon`. `isFullPage(ctx)`
41
+ reads it, and with an older CLI that passes no `fullPage` treats everything
42
+ but a DSM desktop window as a full page. Pass `ctx` to `AppShell` and a full
43
+ page takes the web layout and a device-width viewport, names the App in the
44
+ sidebar and shows the signed-in user, administrator or DSM user, at its foot;
45
+ a DSM window keeps its window layout and title bar. `AppShell` reads only
46
+ `fullPage` (or `host`), `displayName`, `appIcon` and `user` (`ShellContext`),
47
+ so the DSM window's ctx works as it is.
48
+
49
+ ```vue
50
+ <AppShell v-model="page" :ctx="ctx" :locale="ctx.locale" :pages="pages" sidebar-storage-key="HelloGo" />
51
+ ```
52
+
53
+ ## Outside DSM
54
+
55
+ A development page, a preview or a test owns the browser page and has no DSM
56
+ window. `createStandaloneContext({call})` gives it a full-page `Context`
57
+ (`locale: 'en'`, `fullPage: true`, a no-op `reopen`) around the App's own
58
+ `call`; any other field overrides. `createStandaloneHost(platform?)` is its
59
+ `HostAdapter`: `setTitle` titles the document, `openURL` opens an http(s)
60
+ URL in a new tab, `subscribe('window-resize')` follows the browser window,
61
+ and the DSM desktop actions answer `{status: 'unsupported'}`. Platform fields
62
+ override the snapshot, so a test can stand in for a DSM window
63
+ (`createStandaloneHost({host: 'dsm', dsmStandalone: false})`).
64
+
65
+ ```ts
66
+ const ctx = createStandaloneContext({locale: 'zh-TW', call: (method, params) => post('/api', {method, params})})
67
+ ```
68
+
69
+ ## App pages
70
+
71
+ `AppShell` lays out the App's own pages too. Each `AppShellPage` has an `id`,
72
+ a `title` the App has already translated, and optionally an `icon`, a `group`
73
+ label (pages with the same label sit together), a `badge`, a `component` to
74
+ render while it is the current page, `hidden` (reachable through `v-model`
75
+ but not in the sidebar), its own `description`, and `header: false` for a
76
+ page that draws its own header. `fill: true` gives the page's body the height under
77
+ its header and notices; the page does not scroll, so the content scrolls
78
+ itself (a treemap, a table paged by height, a log viewer). The shell then
79
+ holds to `100dvh`, so a phone's address bar does not leave a gap. The built-in `'status'` and `'background'`
80
+ may sit in the same list.
81
+
82
+ ```vue
83
+ <AppShell v-model="page" :ctx="ctx" :locale="ctx.locale" :pages="[
84
+ {id: 'downloads', title: t('downloads'), icon: Download, badge: running || undefined, component: DownloadsPage},
85
+ {id: 'settings', title: t('settings'), icon: Settings, group: t('admin')},
86
+ 'background',
87
+ ]" page-width="fill">
88
+ <template #actions="{page}"><Button v-if="page === 'downloads'" @click="add">{{ t('add') }}</Button></template>
89
+ <template #notices><JobStrip v-for="job in jobs" :key="job.id" :job="job" /></template>
90
+ <template #default="{page}"><SettingsPage v-show="page === 'settings'" /></template>
91
+ </AppShell>
92
+ ```
93
+
94
+ Moving to another page starts it at the top; `keep-scroll` keeps the
95
+ position. The `toolbar` slot sits above the page (in `AppLayout`'s toolbar)
96
+ and `notices` in the page, under its header and inside the same padding, and
97
+ scrolls with it. `activePage(current, pages)` keeps a selection that still exists and
98
+ otherwise picks the first page the sidebar shows. A router drives the shell
99
+ through `v-model`:
100
+ `:model-value="route.name" @update:model-value="name => router.push({name})"`.
101
+
102
+ ## App root
103
+
104
+ `app-root` wraps the shell in an app-local `AppRoot`
105
+ (`modal-scope="app"`, `height: 100%`) and a `ConfigProvider` with the
106
+ shell's `locale`, so the UI components' own text follows it. In a DSM desktop window (a `ctx` that
107
+ is not a full page) its dialogs may leave the window (`dialog-bounds=
108
+ "viewport"`), and a press on a floating dialog calls
109
+ `ctx.host.activateWindow()`, which brings the window to the front; a full
110
+ page and a shell without `ctx` keep dialogs in the App. Use it in place of
111
+ the App's own `AppRoot`: app-local roots do not nest.
112
+
113
+ ```vue
114
+ <AppShell app-root v-model="page" :ctx="ctx" :locale="ctx.locale" :pages="pages" />
115
+ ```
116
+
117
+ ## Denied access
118
+
119
+ `denied` replaces the App's pages with one page that says the account may
120
+ not use the App: a `No access` header and a message pointing to DSM
121
+ Application Privileges, in every UI locale (`shellCopy`'s `deniedTitle` and
122
+ `deniedMessage`). `denied` is `true` or an `AppShellDenied` (`title`,
123
+ `message`, `header: false`); the `denied` slot replaces the message, for
124
+ example with a sign-in form. The sidebar, the notices and the default slot
125
+ are not rendered meanwhile.
126
+
127
+ ```vue
128
+ <AppShell v-model="page" :ctx="ctx" :locale="ctx.locale" :pages="pages" :denied="!authorized && {title: t('privilegeTitle')}" />
129
+ <AppShell v-model="page" :ctx="ctx" :locale="ctx.locale" :pages="pages" :denied="signInNeeded && {header: false}">
130
+ <template #denied><LoginPage :session="login" :locale="ctx.locale" /></template>
131
+ </AppShell>
132
+ ```
133
+
134
+ ## Calling the backend
135
+
136
+ `ctx.call(method, params, {signal})` posts to the package backend. An abort of
137
+ `signal` rejects it with `{code: 'request_canceled'}`, for a page the user
138
+ leaves; past `clientTimeout(ctx.requestTimeout)` it rejects with `{code:
139
+ 'request_timeout'}`.
140
+ Without `ctx.requestTimeout` the deadline is `DEFAULT_REQUEST_TIMEOUT`, 60
141
+ seconds like the backend's, and the client waits 62.
142
+
143
+ ```ts
144
+ const controller = new AbortController()
145
+ const items = await ctx.call<Item[]>('items.list', {}, {signal: controller.signal})
146
+ ```
147
+
37
148
  ## Localized copy
38
149
 
39
150
  `format(message, values, locale)` fills `{key}` placeholders from `values`; a
@@ -52,6 +163,12 @@ format(copy[locale].files, {count: 3, folder: 'photo'}, locale) // {"one": "{cou
52
163
  `mergeCatalogs` layers the App's catalogs over the SDK's, key by key; for each
53
164
  key the order is the App's locale, the SDK's locale, then `en`.
54
165
 
166
+ `bridgeErrorCopy(error, locale)` is the text of a code that the bridge runtime
167
+ adds for Python, Node and Rust Apps: `app_unavailable`, and the services'
168
+ `limit_exceeded`, `read_failed` and `identity_unavailable`. For any other code
169
+ it returns `undefined`, so the App falls back to its own text. Its catalogs,
170
+ `i18n/bridge/<locale>.json`, are generated from `spec/bridge/v1/errors.json`.
171
+
55
172
  The Vue entry needs the peer dependencies `vue`, `@synopackageland/ui` and
56
173
  `@lucide/vue`. The headless entry needs none.
57
174
 
@@ -64,6 +181,10 @@ npm run typecheck
64
181
  npm run build
65
182
  ```
66
183
 
184
+ After a change to `../spec/bridge/v1/errors.json`, run `node
185
+ scripts/gen-bridge-i18n.mjs`; `npm test` fails while `i18n/bridge/` differs
186
+ from it.
187
+
67
188
  `npm run build` also copies `../spec` into `spec/`. Run `npm ci` before
68
189
  `npm pack`: `prepack` builds and needs `node_modules`. `npm install
69
190
  --install-links` from a checkout does not run `prepack`, so build first.
@@ -0,0 +1,6 @@
1
+ {
2
+ "app_unavailable": "Die App ist derzeit nicht verfügbar. Versuchen Sie es später erneut.",
3
+ "limit_exceeded": "Die Datei ist größer als zulässig.",
4
+ "read_failed": "Die Datei kann nicht gelesen werden. Bitte versuchen Sie es später erneut.",
5
+ "identity_unavailable": "Dieses Benutzerkonto kann nicht ermittelt werden. Bitte versuchen Sie es später erneut."
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "app_unavailable": "The App is not available right now. Try again later.",
3
+ "limit_exceeded": "The file is larger than allowed.",
4
+ "read_failed": "Unable to read the file. Please try again later.",
5
+ "identity_unavailable": "Unable to identify this user account. Please try again later."
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "app_unavailable": "La App no está disponible en este momento. Vuelva a intentarlo más tarde.",
3
+ "limit_exceeded": "El archivo supera el tamaño permitido.",
4
+ "read_failed": "No se puede leer el archivo. Vuelva a intentarlo más tarde.",
5
+ "identity_unavailable": "No se puede identificar esta cuenta de usuario. Vuelva a intentarlo más tarde."
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "app_unavailable": "L’App n’est pas disponible pour le moment. Réessayez plus tard.",
3
+ "limit_exceeded": "Le fichier dépasse la taille autorisée.",
4
+ "read_failed": "Impossible de lire le fichier. Veuillez réessayer ultérieurement.",
5
+ "identity_unavailable": "Impossible d'identifier ce compte utilisateur. Veuillez réessayer ultérieurement."
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "app_unavailable": "L’App non è al momento disponibile. Riprovare più tardi.",
3
+ "limit_exceeded": "Il file supera le dimensioni consentite.",
4
+ "read_failed": "Impossibile leggere il file. Riprovare più tardi.",
5
+ "identity_unavailable": "Impossibile identificare questo account utente. Riprovare più tardi."
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "app_unavailable": "App は現在使用できません。しばらくしてから再試行してください。",
3
+ "limit_exceeded": "ファイルが許可されたサイズを超えています。",
4
+ "read_failed": "ファイルを読み取れません。後でもう一度お試しください。",
5
+ "identity_unavailable": "このユーザー アカウントを識別できません。後でもう一度お試しください。"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "app_unavailable": "현재 App을 사용할 수 없습니다. 나중에 다시 시도하십시오.",
3
+ "limit_exceeded": "파일이 허용된 크기를 초과합니다.",
4
+ "read_failed": "파일을 읽을 수 없습니다. 나중에 다시 시도하십시오.",
5
+ "identity_unavailable": "이 사용자 계정을 확인할 수 없습니다. 나중에 다시 시도하십시오."
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "app_unavailable": "App 当前不可用,请稍后重试。",
3
+ "limit_exceeded": "文件超过允许的大小。",
4
+ "read_failed": "无法读取文件。请稍后再试。",
5
+ "identity_unavailable": "无法识别此用户帐号。请稍后再试。"
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "app_unavailable": "App 目前無法使用,請稍後再試。",
3
+ "limit_exceeded": "檔案超過允許的大小。",
4
+ "read_failed": "無法讀取檔案。請稍後再試。",
5
+ "identity_unavailable": "無法識別此使用者帳號。請稍後再試。"
6
+ }
@@ -1,4 +1,8 @@
1
1
  {
2
2
  "status": "Status",
3
- "background": "Hintergrundaufträge"
3
+ "background": "Hintergrundaufträge",
4
+ "administrator": "Administrator",
5
+ "dsmUser": "DSM-Benutzer",
6
+ "deniedTitle": "Kein Zugriff",
7
+ "deniedMessage": "Ihr DSM-Konto darf diese App nicht verwenden. Bitten Sie einen Administrator, sie in den DSM-Anwendungsberechtigungen freizugeben."
4
8
  }
@@ -1,4 +1,8 @@
1
1
  {
2
2
  "status": "Status",
3
- "background": "Background jobs"
3
+ "background": "Background jobs",
4
+ "administrator": "Administrator",
5
+ "dsmUser": "DSM user",
6
+ "deniedTitle": "No access",
7
+ "deniedMessage": "Your DSM account is not allowed to use this app. Ask an administrator to allow it in DSM Application Privileges."
4
8
  }
@@ -1,4 +1,8 @@
1
1
  {
2
2
  "status": "Estado",
3
- "background": "Tareas en segundo plano"
3
+ "background": "Tareas en segundo plano",
4
+ "administrator": "Administrador",
5
+ "dsmUser": "Usuario DSM",
6
+ "deniedTitle": "Sin acceso",
7
+ "deniedMessage": "Su cuenta de DSM no tiene permiso para usar esta aplicación. Pida a un administrador que la permita en los privilegios de aplicaciones de DSM."
4
8
  }
@@ -1,4 +1,8 @@
1
1
  {
2
2
  "status": "Statut",
3
- "background": "Tâches en arrière-plan"
3
+ "background": "Tâches en arrière-plan",
4
+ "administrator": "Administrateur",
5
+ "dsmUser": "Utilisateur DSM",
6
+ "deniedTitle": "Accès refusé",
7
+ "deniedMessage": "Votre compte DSM n’est pas autorisé à utiliser cette application. Demandez à un administrateur de l’autoriser dans les privilèges d’application de DSM."
4
8
  }
@@ -1,4 +1,8 @@
1
1
  {
2
2
  "status": "Stato",
3
- "background": "Attività in background"
3
+ "background": "Attività in background",
4
+ "administrator": "Amministratore",
5
+ "dsmUser": "Utente DSM",
6
+ "deniedTitle": "Accesso negato",
7
+ "deniedMessage": "L’account DSM non è autorizzato a usare questa app. Chiedere a un amministratore di consentirlo nei privilegi delle applicazioni DSM."
4
8
  }
@@ -1,4 +1,8 @@
1
1
  {
2
2
  "status": "状態",
3
- "background": "バックグラウンドジョブ"
3
+ "background": "バックグラウンドジョブ",
4
+ "administrator": "管理者",
5
+ "dsmUser": "DSM ユーザー",
6
+ "deniedTitle": "アクセスできません",
7
+ "deniedMessage": "この DSM アカウントにはこのアプリを使用する権限がありません。DSM のアプリケーション権限で許可するよう管理者に依頼してください。"
4
8
  }
@@ -1,4 +1,8 @@
1
1
  {
2
2
  "status": "상태",
3
- "background": "백그라운드 작업"
3
+ "background": "백그라운드 작업",
4
+ "administrator": "관리자",
5
+ "dsmUser": "DSM 사용자",
6
+ "deniedTitle": "액세스할 수 없음",
7
+ "deniedMessage": "이 DSM 계정에는 이 앱을 사용할 권한이 없습니다. 관리자에게 DSM 응용 프로그램 권한에서 허용하도록 요청하십시오."
4
8
  }
@@ -1,4 +1,8 @@
1
1
  {
2
2
  "status": "状态",
3
- "background": "后台工作"
3
+ "background": "后台工作",
4
+ "administrator": "管理员",
5
+ "dsmUser": "DSM 用户",
6
+ "deniedTitle": "无法访问",
7
+ "deniedMessage": "你的 DSM 帐号无权使用此应用程序。请管理员在 DSM 的应用程序权限中开放给你。"
4
8
  }
@@ -1,4 +1,8 @@
1
1
  {
2
2
  "status": "狀態",
3
- "background": "背景工作"
3
+ "background": "背景工作",
4
+ "administrator": "管理員",
5
+ "dsmUser": "DSM 使用者",
6
+ "deniedTitle": "無法存取",
7
+ "deniedMessage": "你的 DSM 帳號無權使用此應用程式。請管理員在 DSM 的應用程式權限中開放給你。"
4
8
  }