@snaptrude/plugin-core 0.7.1 → 0.9.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 (193) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/api-manifest.full.json +7703 -0
  3. package/api-manifest.json +2946 -259
  4. package/dist/api/analysis/daylight.d.ts +603 -0
  5. package/dist/api/analysis/daylight.d.ts.map +1 -0
  6. package/dist/api/analysis/heatmaps.d.ts +438 -14
  7. package/dist/api/analysis/heatmaps.d.ts.map +1 -1
  8. package/dist/api/analysis/index.d.ts +15 -0
  9. package/dist/api/analysis/index.d.ts.map +1 -1
  10. package/dist/api/analysis/solar.d.ts +249 -0
  11. package/dist/api/analysis/solar.d.ts.map +1 -0
  12. package/dist/api/analysis/weather.d.ts +193 -0
  13. package/dist/api/analysis/weather.d.ts.map +1 -0
  14. package/dist/api/core/camera/index.d.ts +245 -0
  15. package/dist/api/core/camera/index.d.ts.map +1 -0
  16. package/dist/api/core/comment/index.d.ts +105 -2
  17. package/dist/api/core/comment/index.d.ts.map +1 -1
  18. package/dist/api/core/geom/create/index.d.ts +331 -14
  19. package/dist/api/core/geom/create/index.d.ts.map +1 -1
  20. package/dist/api/core/geom/delete/index.d.ts +8 -2
  21. package/dist/api/core/geom/delete/index.d.ts.map +1 -1
  22. package/dist/api/core/geom/query/arc.d.ts +5 -5
  23. package/dist/api/core/geom/query/brep.d.ts +18 -18
  24. package/dist/api/core/geom/query/circle.d.ts +18 -18
  25. package/dist/api/core/geom/query/contour.d.ts +20 -20
  26. package/dist/api/core/geom/query/curve.d.ts +49 -49
  27. package/dist/api/core/geom/query/edge.d.ts +5 -5
  28. package/dist/api/core/geom/query/face.d.ts +16 -16
  29. package/dist/api/core/geom/query/halfedge.d.ts +8 -8
  30. package/dist/api/core/geom/query/profile.d.ts +19 -19
  31. package/dist/api/core/geom/query/vertex.d.ts +8 -8
  32. package/dist/api/core/geom/update/contour.d.ts +14 -14
  33. package/dist/api/core/geom/update/curve.d.ts +7 -7
  34. package/dist/api/core/geom/update/profile.d.ts +16 -16
  35. package/dist/api/core/handles/index.d.ts +210 -0
  36. package/dist/api/core/handles/index.d.ts.map +1 -0
  37. package/dist/api/core/index.d.ts +24 -0
  38. package/dist/api/core/index.d.ts.map +1 -1
  39. package/dist/api/core/io/export/index.d.ts +134 -0
  40. package/dist/api/core/io/export/index.d.ts.map +1 -0
  41. package/dist/api/core/io/import/index.d.ts +62 -1
  42. package/dist/api/core/io/import/index.d.ts.map +1 -1
  43. package/dist/api/core/io/index.d.ts +5 -0
  44. package/dist/api/core/io/index.d.ts.map +1 -1
  45. package/dist/api/core/layers.d.ts +7 -7
  46. package/dist/api/core/mode/index.d.ts +99 -0
  47. package/dist/api/core/mode/index.d.ts.map +1 -0
  48. package/dist/api/core/proposals/index.d.ts +65 -9
  49. package/dist/api/core/proposals/index.d.ts.map +1 -1
  50. package/dist/api/core/storeys/index.d.ts +251 -0
  51. package/dist/api/core/storeys/index.d.ts.map +1 -0
  52. package/dist/api/core/tags.d.ts +24 -0
  53. package/dist/api/core/tags.d.ts.map +1 -1
  54. package/dist/api/core/user.d.ts +44 -0
  55. package/dist/api/core/user.d.ts.map +1 -0
  56. package/dist/api/core/zoom/index.d.ts +4 -0
  57. package/dist/api/core/zoom/index.d.ts.map +1 -1
  58. package/dist/api/design/boolean/index.d.ts +4 -4
  59. package/dist/api/design/create/index.d.ts +253 -47
  60. package/dist/api/design/create/index.d.ts.map +1 -1
  61. package/dist/api/design/delete/index.d.ts +3 -0
  62. package/dist/api/design/delete/index.d.ts.map +1 -1
  63. package/dist/api/design/doors/index.d.ts +36 -0
  64. package/dist/api/design/doors/index.d.ts.map +1 -1
  65. package/dist/api/design/edit/index.d.ts +1 -1
  66. package/dist/api/design/erase/index.d.ts +2 -2
  67. package/dist/api/design/furniture/index.d.ts +114 -3
  68. package/dist/api/design/furniture/index.d.ts.map +1 -1
  69. package/dist/api/design/index.d.ts +10 -0
  70. package/dist/api/design/index.d.ts.map +1 -1
  71. package/dist/api/design/materials/index.d.ts +111 -14
  72. package/dist/api/design/materials/index.d.ts.map +1 -1
  73. package/dist/api/design/query/geometry/index.d.ts +112 -0
  74. package/dist/api/design/query/geometry/index.d.ts.map +1 -1
  75. package/dist/api/design/query/index.d.ts +35 -1
  76. package/dist/api/design/query/index.d.ts.map +1 -1
  77. package/dist/api/design/query/referenceLines.d.ts +45 -0
  78. package/dist/api/design/query/referenceLines.d.ts.map +1 -0
  79. package/dist/api/design/query/spaces.d.ts +178 -5
  80. package/dist/api/design/query/spaces.d.ts.map +1 -1
  81. package/dist/api/design/transform/index.d.ts +95 -14
  82. package/dist/api/design/transform/index.d.ts.map +1 -1
  83. package/dist/api/design/types/index.d.ts +181 -0
  84. package/dist/api/design/types/index.d.ts.map +1 -0
  85. package/dist/api/design/update/index.d.ts +385 -2
  86. package/dist/api/design/update/index.d.ts.map +1 -1
  87. package/dist/api/design/visibility.d.ts +98 -0
  88. package/dist/api/design/visibility.d.ts.map +1 -0
  89. package/dist/api/entity/buildableEnvelope.d.ts +4 -0
  90. package/dist/api/entity/buildableEnvelope.d.ts.map +1 -1
  91. package/dist/api/entity/referenceLine.d.ts +10 -2
  92. package/dist/api/entity/referenceLine.d.ts.map +1 -1
  93. package/dist/api/entity/space.d.ts +19 -19
  94. package/dist/api/entity/story.d.ts +148 -15
  95. package/dist/api/entity/story.d.ts.map +1 -1
  96. package/dist/api/index.d.ts +5 -0
  97. package/dist/api/index.d.ts.map +1 -1
  98. package/dist/api/presentation/annotate.d.ts +448 -0
  99. package/dist/api/presentation/annotate.d.ts.map +1 -0
  100. package/dist/api/presentation/diagrams.d.ts +49 -8
  101. package/dist/api/presentation/diagrams.d.ts.map +1 -1
  102. package/dist/api/presentation/export.d.ts +108 -0
  103. package/dist/api/presentation/export.d.ts.map +1 -0
  104. package/dist/api/presentation/import.d.ts +6 -0
  105. package/dist/api/presentation/import.d.ts.map +1 -1
  106. package/dist/api/presentation/index.d.ts +47 -0
  107. package/dist/api/presentation/index.d.ts.map +1 -1
  108. package/dist/api/presentation/placedViews.d.ts +370 -0
  109. package/dist/api/presentation/placedViews.d.ts.map +1 -0
  110. package/dist/api/presentation/shapes.d.ts +481 -0
  111. package/dist/api/presentation/shapes.d.ts.map +1 -0
  112. package/dist/api/presentation/sheets.d.ts +410 -13
  113. package/dist/api/presentation/sheets.d.ts.map +1 -1
  114. package/dist/api/presentation/views.d.ts +165 -10
  115. package/dist/api/presentation/views.d.ts.map +1 -1
  116. package/dist/api/program/areas.d.ts +63 -3
  117. package/dist/api/program/areas.d.ts.map +1 -1
  118. package/dist/api/program/cores.d.ts +3 -99
  119. package/dist/api/program/cores.d.ts.map +1 -1
  120. package/dist/api/program/index.d.ts +2 -2
  121. package/dist/api/program/index.d.ts.map +1 -1
  122. package/dist/api/program/layout.d.ts +172 -12
  123. package/dist/api/program/layout.d.ts.map +1 -1
  124. package/dist/api/program/site.d.ts +105 -8
  125. package/dist/api/program/site.d.ts.map +1 -1
  126. package/dist/api/program/spreadsheet.d.ts +365 -41
  127. package/dist/api/program/spreadsheet.d.ts.map +1 -1
  128. package/dist/api/workspace/index.d.ts +460 -0
  129. package/dist/api/workspace/index.d.ts.map +1 -0
  130. package/dist/handles.d.ts +64 -25
  131. package/dist/handles.d.ts.map +1 -1
  132. package/dist/index.cjs +3210 -1671
  133. package/dist/index.cjs.map +1 -1
  134. package/dist/index.js +3008 -1666
  135. package/dist/index.js.map +1 -1
  136. package/package.json +3 -2
  137. package/scripts/generate-manifest.mjs +45 -0
  138. package/src/api/analysis/daylight.ts +470 -0
  139. package/src/api/analysis/heatmaps.ts +444 -17
  140. package/src/api/analysis/index.ts +15 -0
  141. package/src/api/analysis/solar.ts +237 -0
  142. package/src/api/analysis/weather.ts +179 -0
  143. package/src/api/core/camera/index.ts +251 -0
  144. package/src/api/core/comment/index.ts +120 -2
  145. package/src/api/core/geom/create/index.ts +344 -1
  146. package/src/api/core/geom/delete/index.ts +6 -0
  147. package/src/api/core/handles/index.ts +233 -0
  148. package/src/api/core/index.ts +24 -0
  149. package/src/api/core/io/export/index.ts +126 -0
  150. package/src/api/core/io/import/index.ts +64 -0
  151. package/src/api/core/io/index.ts +5 -0
  152. package/src/api/core/mode/index.ts +96 -0
  153. package/src/api/core/proposals/index.ts +71 -11
  154. package/src/api/core/storeys/index.ts +279 -0
  155. package/src/api/core/tags.ts +27 -0
  156. package/src/api/core/user.ts +46 -0
  157. package/src/api/core/zoom/index.ts +4 -0
  158. package/src/api/design/create/index.ts +303 -36
  159. package/src/api/design/delete/index.ts +3 -0
  160. package/src/api/design/doors/index.ts +40 -0
  161. package/src/api/design/furniture/index.ts +127 -3
  162. package/src/api/design/index.ts +10 -0
  163. package/src/api/design/materials/index.ts +157 -30
  164. package/src/api/design/query/geometry/index.ts +125 -3
  165. package/src/api/design/query/index.ts +37 -7
  166. package/src/api/design/query/referenceLines.ts +52 -0
  167. package/src/api/design/query/spaces.ts +143 -0
  168. package/src/api/design/transform/index.ts +101 -12
  169. package/src/api/design/types/index.ts +156 -0
  170. package/src/api/design/update/index.ts +467 -6
  171. package/src/api/design/visibility.ts +109 -0
  172. package/src/api/entity/buildableEnvelope.ts +4 -0
  173. package/src/api/entity/referenceLine.ts +8 -0
  174. package/src/api/entity/story.ts +161 -15
  175. package/src/api/index.ts +5 -0
  176. package/src/api/presentation/annotate.ts +360 -0
  177. package/src/api/presentation/diagrams.ts +53 -8
  178. package/src/api/presentation/export.ts +108 -0
  179. package/src/api/presentation/import.ts +6 -0
  180. package/src/api/presentation/index.ts +55 -0
  181. package/src/api/presentation/placedViews.ts +363 -0
  182. package/src/api/presentation/shapes.ts +274 -0
  183. package/src/api/presentation/sheets.ts +346 -13
  184. package/src/api/presentation/views.ts +164 -12
  185. package/src/api/program/areas.ts +57 -6
  186. package/src/api/program/cores.ts +3 -91
  187. package/src/api/program/index.ts +2 -2
  188. package/src/api/program/layout.ts +182 -12
  189. package/src/api/program/site.ts +106 -8
  190. package/src/api/program/spreadsheet.ts +376 -35
  191. package/src/api/workspace/index.ts +516 -0
  192. package/src/handles.ts +77 -13
  193. package/tsconfig.json +7 -2
@@ -0,0 +1,516 @@
1
+ import * as z from "zod"
2
+ import { PluginApiReturn } from "../../types"
3
+
4
+ /**
5
+ * `snaptrude.workspace.*` — the dashboard surface: projects and teams outside the
6
+ * open canvas.
7
+ *
8
+ * This namespace reads and creates the containers a design lives in — the
9
+ * project itself and the team it belongs to — mirroring the Snaptrude dashboard.
10
+ * Teams are **read-only** here (no create/invite/delete in v1, a deliberate
11
+ * safety decision); there are no folders in v1. A `projectId` is the project's
12
+ * floorkey. Plan limits are enforced by the backend — a limit rejection surfaces
13
+ * as a normal host error.
14
+ *
15
+ * - {@linkcode PluginWorkspaceApi.projects} — Create, copy, list, read & rename projects
16
+ * - {@linkcode PluginWorkspaceApi.teams} — Read teams and their members
17
+ */
18
+ export abstract class PluginWorkspaceApi {
19
+ /** Projects — create, copy, list, read & rename. See {@linkcode PluginWorkspaceProjectsApi}. */
20
+ public abstract projects: PluginWorkspaceProjectsApi
21
+ /** Teams — read teams and their members. See {@linkcode PluginWorkspaceTeamsApi}. */
22
+ public abstract teams: PluginWorkspaceTeamsApi
23
+
24
+ constructor() {}
25
+ }
26
+
27
+ /**
28
+ * `workspace.projects.*` — create, copy, list, read & rename projects.
29
+ *
30
+ * A **project** is a single Snaptrude model, identified by its `projectId`
31
+ * (floorkey). Reads never throw for a miss ({@linkcode PluginWorkspaceProjectsApi.get}
32
+ * returns `null`). Create/copy/rename go through the backend, which enforces plan
33
+ * limits — a rejection surfaces as a normal host error.
34
+ *
35
+ * Accessed via `snaptrude.workspace.projects`.
36
+ */
37
+ export abstract class PluginWorkspaceProjectsApi {
38
+ constructor() {}
39
+
40
+ /**
41
+ * Create a new, empty BIM project.
42
+ *
43
+ * Creates the project on the backend and returns its `projectId` (floorkey).
44
+ * Pass `options.teamId` to create it inside a team (otherwise it lands in the
45
+ * user's personal workspace) and `options.unit` to set the default length unit.
46
+ *
47
+ * @param name - Display name of the new project.
48
+ * @param options - Optional `unit` (default length unit) and `teamId` (owning team).
49
+ * @returns The new project's `projectId`.
50
+ * @throws If the backend rejects the create (e.g. a plan project limit).
51
+ *
52
+ * @examplePrompt Create a new project called Tower Study
53
+ * @examplePrompt Start a new project in the Acme team
54
+ * @examplePrompt Make a fresh project using millimeters
55
+ * @examplePrompt Create an empty project for me to work in
56
+ *
57
+ * # Example
58
+ * ```ts
59
+ * const { projectId } = await snaptrude.workspace.projects.create("Tower Study", {
60
+ * teamId: "team_1",
61
+ * unit: "mm",
62
+ * })
63
+ * ```
64
+ */
65
+ public abstract create(
66
+ name: string,
67
+ options?: { unit?: string; teamId?: string },
68
+ ): PluginApiReturn<PluginWorkspaceProjectsCreateResult>
69
+
70
+ /**
71
+ * Copy the **current** project into a new project — the dashboard's "Save As".
72
+ *
73
+ * Duplicates the project that is currently open into a new project with the
74
+ * given name, and returns the new `projectId`. Pass `options.teamId` to place
75
+ * the copy in a specific team.
76
+ *
77
+ * @param name - Display name for the copy.
78
+ * @param options - Optional `teamId` (team to place the copy in).
79
+ * @returns The new project's `projectId`.
80
+ * @throws If the backend rejects the copy (e.g. a plan project limit).
81
+ *
82
+ * @examplePrompt Save this project as a new copy called Tower Study v2
83
+ * @examplePrompt Duplicate the current project into the Acme team
84
+ * @examplePrompt Make a copy of this project so I can try changes
85
+ *
86
+ * # Example
87
+ * ```ts
88
+ * const { projectId } = await snaptrude.workspace.projects.copy("Tower Study v2")
89
+ * ```
90
+ */
91
+ public abstract copy(
92
+ name: string,
93
+ options?: { teamId?: string },
94
+ ): PluginApiReturn<PluginWorkspaceProjectsCopyResult>
95
+
96
+ /**
97
+ * List the projects the user can access.
98
+ *
99
+ * Returns a {@linkcode PluginProjectRef} per project. Pass `options.teamId` to
100
+ * scope the list to a single team; omit it for the user's accessible projects.
101
+ *
102
+ * @param options - Optional `teamId` filter.
103
+ * @returns A {@linkcode PluginWorkspaceProjectsListResult} with a `projects`
104
+ * array (empty when there are none).
105
+ *
106
+ * @examplePrompt List all my projects
107
+ * @examplePrompt Show the projects in the Acme team
108
+ * @examplePrompt How many projects do I have?
109
+ * @examplePrompt What projects can I open?
110
+ *
111
+ * # Example
112
+ * ```ts
113
+ * const { projects } = await snaptrude.workspace.projects.list({ teamId: "team_1" })
114
+ * for (const p of projects) console.log(p.id, p.name)
115
+ * ```
116
+ */
117
+ public abstract list(
118
+ options?: { teamId?: string },
119
+ ): PluginApiReturn<PluginWorkspaceProjectsListResult>
120
+
121
+ /**
122
+ * Get a single project by id.
123
+ *
124
+ * @param projectId - The project's `projectId` (floorkey).
125
+ * @returns The matching {@linkcode PluginProjectRef}, or `null` if no project
126
+ * has that id / the user cannot access it.
127
+ *
128
+ * @examplePrompt Get the project with this id
129
+ * @examplePrompt Look up a project by its floorkey
130
+ * @examplePrompt What is the name of this project?
131
+ *
132
+ * # Example
133
+ * ```ts
134
+ * const project = await snaptrude.workspace.projects.get("floorkey_123")
135
+ * if (project) console.log(project.name)
136
+ * ```
137
+ */
138
+ public abstract get(
139
+ projectId: string,
140
+ ): PluginApiReturn<PluginWorkspaceProjectsGetResult>
141
+
142
+ /**
143
+ * Rename a project.
144
+ *
145
+ * @param projectId - The project's `projectId` (floorkey).
146
+ * @param name - The project's new display name.
147
+ * @returns The renamed project's `projectId`.
148
+ * @throws If no project has the given id or the rename is rejected.
149
+ *
150
+ * @examplePrompt Rename this project to Tower Study Final
151
+ * @examplePrompt Change the name of project floorkey_123
152
+ * @examplePrompt Give this project a new name
153
+ *
154
+ * # Example
155
+ * ```ts
156
+ * const { projectId } = await snaptrude.workspace.projects.rename(
157
+ * "floorkey_123",
158
+ * "Tower Study Final",
159
+ * )
160
+ * ```
161
+ */
162
+ public abstract rename(
163
+ projectId: string,
164
+ name: string,
165
+ ): PluginApiReturn<PluginWorkspaceProjectsRenameResult>
166
+ }
167
+
168
+ /**
169
+ * `workspace.teams.*` — read teams and their members.
170
+ *
171
+ * A **team** is a shared workspace that owns projects and members. **Read-only**
172
+ * in v1: there is no create/invite/delete (a deliberate safety decision). Reads
173
+ * never throw for a miss ({@linkcode PluginWorkspaceTeamsApi.get} returns `null`).
174
+ *
175
+ * Accessed via `snaptrude.workspace.teams`.
176
+ */
177
+ export abstract class PluginWorkspaceTeamsApi {
178
+ constructor() {}
179
+
180
+ /**
181
+ * List the teams the user belongs to.
182
+ *
183
+ * @returns A {@linkcode PluginWorkspaceTeamsListResult} with a `teams` array
184
+ * (empty when the user belongs to none).
185
+ *
186
+ * @examplePrompt List all my teams
187
+ * @examplePrompt Which teams am I a member of?
188
+ * @examplePrompt Show every team I can access
189
+ *
190
+ * # Example
191
+ * ```ts
192
+ * const { teams } = await snaptrude.workspace.teams.list()
193
+ * for (const t of teams) console.log(t.id, t.name)
194
+ * ```
195
+ */
196
+ public abstract list(): PluginApiReturn<PluginWorkspaceTeamsListResult>
197
+
198
+ /**
199
+ * Get a single team by id.
200
+ *
201
+ * @param teamId - The id of the team to read.
202
+ * @returns The matching {@linkcode PluginTeamRef}, or `null` if no team has that
203
+ * id / the user is not a member.
204
+ *
205
+ * @examplePrompt Get the team with id team_1
206
+ * @examplePrompt Look up a team by its id
207
+ * @examplePrompt What is the name of this team?
208
+ *
209
+ * # Example
210
+ * ```ts
211
+ * const team = await snaptrude.workspace.teams.get("team_1")
212
+ * if (team) console.log(team.name)
213
+ * ```
214
+ */
215
+ public abstract get(
216
+ teamId: string,
217
+ ): PluginApiReturn<PluginWorkspaceTeamsGetResult>
218
+
219
+ /**
220
+ * List the members of a team.
221
+ *
222
+ * @param teamId - The id of the team whose members to list.
223
+ * @returns A {@linkcode PluginWorkspaceTeamsListMembersResult} with a `members`
224
+ * array (empty when the team has none / is missing).
225
+ *
226
+ * @examplePrompt List the members of the Acme team
227
+ * @examplePrompt Who is on team_1?
228
+ * @examplePrompt How many people are in this team?
229
+ *
230
+ * # Example
231
+ * ```ts
232
+ * const { members } = await snaptrude.workspace.teams.listMembers("team_1")
233
+ * for (const m of members) console.log(m.name, m.email)
234
+ * ```
235
+ */
236
+ public abstract listMembers(
237
+ teamId: string,
238
+ ): PluginApiReturn<PluginWorkspaceTeamsListMembersResult>
239
+ }
240
+
241
+ // ---------------------------------------------------------------------------
242
+ // DTOs — conservative, never raw backend payloads
243
+ // ---------------------------------------------------------------------------
244
+
245
+ /**
246
+ * A project reference — a conservative projection of a project, never the raw
247
+ * backend payload.
248
+ *
249
+ * | Property | Type | Description |
250
+ * |---|---|---|
251
+ * | `id` | `string` | The project's `projectId` (floorkey) |
252
+ * | `name` | `string` | Display name |
253
+ * | `teamId` | `string?` | Owning team id (absent for personal projects) |
254
+ * | `createdAt` | `string?` | Creation timestamp (ISO 8601) when known |
255
+ * | `modifiedAt` | `string?` | Last-modified timestamp (ISO 8601) when known |
256
+ */
257
+ export const PluginProjectRef = z.object({
258
+ id: z.string(),
259
+ name: z.string(),
260
+ teamId: z.string().optional(),
261
+ createdAt: z.string().optional(),
262
+ modifiedAt: z.string().optional(),
263
+ })
264
+ export type PluginProjectRef = z.infer<typeof PluginProjectRef>
265
+
266
+ /**
267
+ * A team reference — a conservative projection of a team, never the raw backend
268
+ * payload.
269
+ *
270
+ * | Property | Type | Description |
271
+ * |---|---|---|
272
+ * | `id` | `string` | Stable team id |
273
+ * | `name` | `string` | Display name |
274
+ * | `role` | `string?` | The user's role in the team when known |
275
+ */
276
+ export const PluginTeamRef = z.object({
277
+ id: z.string(),
278
+ name: z.string(),
279
+ role: z.string().optional(),
280
+ })
281
+ export type PluginTeamRef = z.infer<typeof PluginTeamRef>
282
+
283
+ /**
284
+ * A team member reference — a conservative projection of a member, never the raw
285
+ * backend payload.
286
+ *
287
+ * | Property | Type | Description |
288
+ * |---|---|---|
289
+ * | `id` | `string` | Stable user id |
290
+ * | `name` | `string` | Display name |
291
+ * | `email` | `string?` | Email address when known |
292
+ * | `role` | `string?` | The member's role in the team when known |
293
+ */
294
+ export const PluginTeamMemberRef = z.object({
295
+ id: z.string(),
296
+ name: z.string(),
297
+ email: z.string().optional(),
298
+ role: z.string().optional(),
299
+ })
300
+ export type PluginTeamMemberRef = z.infer<typeof PluginTeamMemberRef>
301
+
302
+ // ---------------------------------------------------------------------------
303
+ // projects — args + results
304
+ // ---------------------------------------------------------------------------
305
+
306
+ /**
307
+ * Arguments for {@linkcode PluginWorkspaceProjectsApi.create} (options flattened).
308
+ *
309
+ * | Property | Type | Description |
310
+ * |---|---|---|
311
+ * | `name` | `string` | Display name of the new project |
312
+ * | `unit` | `string?` | Default length unit |
313
+ * | `teamId` | `string?` | Owning team id (personal workspace if omitted) |
314
+ */
315
+ export const PluginWorkspaceProjectsCreateArgs = z.object({
316
+ name: z.string(),
317
+ unit: z.string().optional(),
318
+ teamId: z.string().optional(),
319
+ })
320
+ export type PluginWorkspaceProjectsCreateArgs = z.infer<
321
+ typeof PluginWorkspaceProjectsCreateArgs
322
+ >
323
+
324
+ /**
325
+ * Result of {@linkcode PluginWorkspaceProjectsApi.create}.
326
+ *
327
+ * | Property | Type | Description |
328
+ * |---|---|---|
329
+ * | `projectId` | `string` | The id (floorkey) of the created project |
330
+ */
331
+ export const PluginWorkspaceProjectsCreateResult = z.object({
332
+ projectId: z.string(),
333
+ })
334
+ export type PluginWorkspaceProjectsCreateResult = z.infer<
335
+ typeof PluginWorkspaceProjectsCreateResult
336
+ >
337
+
338
+ /**
339
+ * Arguments for {@linkcode PluginWorkspaceProjectsApi.copy} (options flattened).
340
+ *
341
+ * | Property | Type | Description |
342
+ * |---|---|---|
343
+ * | `name` | `string` | Display name for the copy |
344
+ * | `teamId` | `string?` | Team to place the copy in |
345
+ */
346
+ export const PluginWorkspaceProjectsCopyArgs = z.object({
347
+ name: z.string(),
348
+ teamId: z.string().optional(),
349
+ })
350
+ export type PluginWorkspaceProjectsCopyArgs = z.infer<
351
+ typeof PluginWorkspaceProjectsCopyArgs
352
+ >
353
+
354
+ /**
355
+ * Result of {@linkcode PluginWorkspaceProjectsApi.copy}.
356
+ *
357
+ * | Property | Type | Description |
358
+ * |---|---|---|
359
+ * | `projectId` | `string` | The id (floorkey) of the copied project |
360
+ */
361
+ export const PluginWorkspaceProjectsCopyResult = z.object({
362
+ projectId: z.string(),
363
+ })
364
+ export type PluginWorkspaceProjectsCopyResult = z.infer<
365
+ typeof PluginWorkspaceProjectsCopyResult
366
+ >
367
+
368
+ /**
369
+ * Arguments for {@linkcode PluginWorkspaceProjectsApi.list} (options flattened).
370
+ *
371
+ * | Property | Type | Description |
372
+ * |---|---|---|
373
+ * | `teamId` | `string?` | Scope the list to this team; omit for all accessible projects |
374
+ */
375
+ export const PluginWorkspaceProjectsListArgs = z.object({
376
+ teamId: z.string().optional(),
377
+ })
378
+ export type PluginWorkspaceProjectsListArgs = z.infer<
379
+ typeof PluginWorkspaceProjectsListArgs
380
+ >
381
+
382
+ /**
383
+ * Result of {@linkcode PluginWorkspaceProjectsApi.list}.
384
+ *
385
+ * | Property | Type | Description |
386
+ * |---|---|---|
387
+ * | `projects` | {@linkcode PluginProjectRef}`[]` | Accessible projects (empty when none) |
388
+ */
389
+ export const PluginWorkspaceProjectsListResult = z.object({
390
+ projects: z.array(PluginProjectRef),
391
+ })
392
+ export type PluginWorkspaceProjectsListResult = z.infer<
393
+ typeof PluginWorkspaceProjectsListResult
394
+ >
395
+
396
+ /**
397
+ * Arguments for {@linkcode PluginWorkspaceProjectsApi.get}.
398
+ *
399
+ * | Property | Type | Description |
400
+ * |---|---|---|
401
+ * | `projectId` | `string` | The project's `projectId` (floorkey) |
402
+ */
403
+ export const PluginWorkspaceProjectsGetArgs = z.object({
404
+ projectId: z.string(),
405
+ })
406
+ export type PluginWorkspaceProjectsGetArgs = z.infer<
407
+ typeof PluginWorkspaceProjectsGetArgs
408
+ >
409
+
410
+ /**
411
+ * Result of {@linkcode PluginWorkspaceProjectsApi.get} — the project, or `null`
412
+ * when no project has the given id.
413
+ */
414
+ export const PluginWorkspaceProjectsGetResult = PluginProjectRef.nullable()
415
+ export type PluginWorkspaceProjectsGetResult = z.infer<
416
+ typeof PluginWorkspaceProjectsGetResult
417
+ >
418
+
419
+ /**
420
+ * Arguments for {@linkcode PluginWorkspaceProjectsApi.rename}.
421
+ *
422
+ * | Property | Type | Description |
423
+ * |---|---|---|
424
+ * | `projectId` | `string` | The project's `projectId` (floorkey) |
425
+ * | `name` | `string` | The project's new display name |
426
+ */
427
+ export const PluginWorkspaceProjectsRenameArgs = z.object({
428
+ projectId: z.string(),
429
+ name: z.string(),
430
+ })
431
+ export type PluginWorkspaceProjectsRenameArgs = z.infer<
432
+ typeof PluginWorkspaceProjectsRenameArgs
433
+ >
434
+
435
+ /**
436
+ * Result of {@linkcode PluginWorkspaceProjectsApi.rename}.
437
+ *
438
+ * | Property | Type | Description |
439
+ * |---|---|---|
440
+ * | `projectId` | `string` | The id (floorkey) of the renamed project |
441
+ */
442
+ export const PluginWorkspaceProjectsRenameResult = z.object({
443
+ projectId: z.string(),
444
+ })
445
+ export type PluginWorkspaceProjectsRenameResult = z.infer<
446
+ typeof PluginWorkspaceProjectsRenameResult
447
+ >
448
+
449
+ // ---------------------------------------------------------------------------
450
+ // teams — args + results
451
+ // ---------------------------------------------------------------------------
452
+
453
+ /**
454
+ * Result of {@linkcode PluginWorkspaceTeamsApi.list}.
455
+ *
456
+ * | Property | Type | Description |
457
+ * |---|---|---|
458
+ * | `teams` | {@linkcode PluginTeamRef}`[]` | The user's teams (empty when none) |
459
+ */
460
+ export const PluginWorkspaceTeamsListResult = z.object({
461
+ teams: z.array(PluginTeamRef),
462
+ })
463
+ export type PluginWorkspaceTeamsListResult = z.infer<
464
+ typeof PluginWorkspaceTeamsListResult
465
+ >
466
+
467
+ /**
468
+ * Arguments for {@linkcode PluginWorkspaceTeamsApi.get}.
469
+ *
470
+ * | Property | Type | Description |
471
+ * |---|---|---|
472
+ * | `teamId` | `string` | The id of the team to read |
473
+ */
474
+ export const PluginWorkspaceTeamsGetArgs = z.object({
475
+ teamId: z.string(),
476
+ })
477
+ export type PluginWorkspaceTeamsGetArgs = z.infer<
478
+ typeof PluginWorkspaceTeamsGetArgs
479
+ >
480
+
481
+ /**
482
+ * Result of {@linkcode PluginWorkspaceTeamsApi.get} — the team, or `null` when no
483
+ * team has the given id.
484
+ */
485
+ export const PluginWorkspaceTeamsGetResult = PluginTeamRef.nullable()
486
+ export type PluginWorkspaceTeamsGetResult = z.infer<
487
+ typeof PluginWorkspaceTeamsGetResult
488
+ >
489
+
490
+ /**
491
+ * Arguments for {@linkcode PluginWorkspaceTeamsApi.listMembers}.
492
+ *
493
+ * | Property | Type | Description |
494
+ * |---|---|---|
495
+ * | `teamId` | `string` | The id of the team whose members to list |
496
+ */
497
+ export const PluginWorkspaceTeamsListMembersArgs = z.object({
498
+ teamId: z.string(),
499
+ })
500
+ export type PluginWorkspaceTeamsListMembersArgs = z.infer<
501
+ typeof PluginWorkspaceTeamsListMembersArgs
502
+ >
503
+
504
+ /**
505
+ * Result of {@linkcode PluginWorkspaceTeamsApi.listMembers}.
506
+ *
507
+ * | Property | Type | Description |
508
+ * |---|---|---|
509
+ * | `members` | {@linkcode PluginTeamMemberRef}`[]` | The team's members (empty when none) |
510
+ */
511
+ export const PluginWorkspaceTeamsListMembersResult = z.object({
512
+ members: z.array(PluginTeamMemberRef),
513
+ })
514
+ export type PluginWorkspaceTeamsListMembersResult = z.infer<
515
+ typeof PluginWorkspaceTeamsListMembersResult
516
+ >
package/src/handles.ts CHANGED
@@ -1,22 +1,71 @@
1
1
  import * as z from "zod"
2
+ import type { ArenaKind } from "./api/core/handles"
3
+
4
+ // Runtime polyfill: engines without explicit-resource-management would otherwise
5
+ // evaluate the class's computed [Symbol.asyncDispose] key as undefined and
6
+ // silently create a property literally named "undefined".
7
+ ;(Symbol as { asyncDispose?: symbol }).asyncDispose ??= Symbol.for("Symbol.asyncDispose")
2
8
 
3
9
  /**
4
10
  * An opaque, kind-tagged reference to a host-side object that crosses the RPC
5
11
  * boundary by IDENTITY rather than by value. See `PLUGIN_API_EXPOSURE_HLD.md` §9.
6
12
  *
7
- * The phantom `__handle` brand makes a `Handle<"profile">` incompatible with a
8
- * `Handle<"mass">` at compile time. At runtime a handle is just a string of the
9
- * form `"<kind>_<token>"` (e.g. `"profile_k3f9a2x"`), so it is structured-clone
10
- * safe and engine-free plugin-core never imports the engine type it points at.
13
+ * A handle is a small CLASS instance wrapping the wire id. The phantom
14
+ * `__handle` brand makes a `Handle<"profile">` incompatible with a
15
+ * `Handle<"vec3">` at compile time. On the wire the id string is all that
16
+ * travels: the client SDK unwraps `Handle` arguments to `.id` before the RPC
17
+ * call, and the host tags returned handles as `{ __h: "<id>" }` (via
18
+ * {@linkcode Handle.toJSON}) for the client to re-wrap.
19
+ *
20
+ * Plugin-side lifecycle: the client SDK interns one live instance per id and
21
+ * registers it with a FinalizationRegistry — dropping every reference to a
22
+ * handle eventually releases its host registry entry. Deterministic release is
23
+ * still preferred: `core.handles.release/releaseAll` or scopes.
24
+ *
25
+ * Crossing non-RPC boundaries (popup UI postMessage, persistence, logging):
26
+ * send {@linkcode Handle.id} — structured clone strips the class prototype.
11
27
  */
12
- export type Handle<K extends string> = string & { readonly __handle: K }
28
+ export class Handle<K extends string> {
29
+ /** Phantom brand — keeps `Handle<"vec3">` ≠ `Handle<"mass">` at compile time. */
30
+ declare readonly __handle: K
31
+
32
+ constructor(readonly id: string) {}
33
+
34
+ /** Wire tag — the host's result serialization (JSON round-trip) calls this. */
35
+ toJSON(): { __h: string } {
36
+ return { __h: this.id }
37
+ }
38
+
39
+ /** Template-string / logging form stays the bare id. */
40
+ toString(): string {
41
+ return this.id
42
+ }
43
+
44
+ /** Identity comparison by id — safe across re-wraps. */
45
+ equals(other: Handle<string>): boolean {
46
+ return this.id === other.id
47
+ }
48
+
49
+ /**
50
+ * Explicit-resource-management hook (`await using`). A no-op here; the
51
+ * plugin-client runtime installs the real enqueue-release behavior on the
52
+ * interned instance.
53
+ */
54
+ async [Symbol.asyncDispose](): Promise<void> {}
55
+ }
56
+
57
+ /** Union of all releasable (arena-backed) handle kinds. */
58
+ export type AnyArenaHandle = Handle<ArenaKind>
13
59
 
14
60
  /**
15
61
  * A scene-entity handle. Backed by the host `Component.id` and resolved live via
16
62
  * the host scene index (StructureCollection) — never stored in the resource
17
63
  * registry. Stable across undo/redo and persistable across sessions (§9.2).
64
+ * Entity handles remain raw branded STRINGS (not {@linkcode Handle} instances):
65
+ * they have no arena entry, no lifecycle, and are equality-matched against
66
+ * plain ids embedded in result records.
18
67
  */
19
- export type EntityId<K extends string = string> = Handle<K>
68
+ export type EntityId<K extends string = string> = string & { readonly __handle: K }
20
69
 
21
70
  /** Resource handle: a host-resident, session-ephemeral geometry object (§9.2). */
22
71
  export type ProfileHandle = Handle<"profile">
@@ -119,19 +168,34 @@ export type BBoxComponents = { min: Vec3Components; max: Vec3Components }
119
168
 
120
169
  /**
121
170
  * Build a Zod schema validating a handle of the given `kind` at the trust
122
- * boundary. It checks only the `"<kind>_"` prefix shape; existence, kind, and
123
- * ownership are enforced host-side by the HandleRegistry (§9.7). Keeping it to
124
- * the prefix is engine-free and avoids an existence oracle.
171
+ * boundary and emitting a {@linkcode Handle} instance for impl code. Inbound
172
+ * forms accepted:
173
+ * - the bare id string (the wire form the client unwraps args to ids)
174
+ * - a live `Handle` instance (host-internal re-parse, direct host callers)
175
+ * - `{ __h: string }` (the tagged result form, echoed back as an arg)
176
+ * - `{ id: string }` (a Handle stripped by structured clone at the popup-UI boundary)
177
+ * Shape checking stays prefix-only; existence, kind, and ownership are enforced
178
+ * host-side by the HandleRegistry (§9.7) — no existence oracle.
125
179
  */
126
180
  export const handleSchema = <K extends string>(kind: K) =>
127
181
  z
128
- .string()
129
- .regex(new RegExp(`^${kind}_[A-Za-z0-9_-]+$`), `expected a "${kind}" handle`)
130
- .transform((s) => s as Handle<K>)
182
+ .preprocess(
183
+ (v) => {
184
+ if (typeof v === "string") return v
185
+ if (v instanceof Handle) return v.id
186
+ if (v && typeof v === "object") {
187
+ const o = v as Record<string, unknown>
188
+ if (typeof o.__h === "string") return o.__h
189
+ if (typeof o.id === "string") return o.id
190
+ }
191
+ return v
192
+ },
193
+ z.string().regex(new RegExp(`^${kind}_[A-Za-z0-9_-]+$`), `expected a "${kind}" handle`)
194
+ )
195
+ .transform((s) => new Handle<K>(s))
131
196
 
132
197
  export const ProfileHandle = handleSchema("profile")
133
198
  export const ContourHandle = handleSchema("contour")
134
- export const MassHandle = handleSchema("mass")
135
199
 
136
200
  /**
137
201
  * Entity handles are raw `Component.id` strings (no `"<kind>_"` prefix) — validated
package/tsconfig.json CHANGED
@@ -3,7 +3,10 @@
3
3
  "target": "ES2020",
4
4
  "module": "ES2020",
5
5
  "moduleResolution": "bundler",
6
- "lib": ["ES2020"],
6
+ "lib": [
7
+ "ES2020",
8
+ "ESNext.Disposable"
9
+ ],
7
10
  "strict": true,
8
11
  "esModuleInterop": true,
9
12
  "skipLibCheck": true,
@@ -13,5 +16,7 @@
13
16
  "declarationMap": true,
14
17
  "outDir": "dist"
15
18
  },
16
- "include": ["src/**/*.ts"]
19
+ "include": [
20
+ "src/**/*.ts"
21
+ ]
17
22
  }