@plitzi/sdk-server 0.37.1 → 0.37.3

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 (386) hide show
  1. package/CHANGELOG.md +1296 -0
  2. package/README.md +126 -7
  3. package/dist/actions.d.ts +20 -8
  4. package/dist/actions.js +11 -4
  5. package/dist/adapters/cloudAdapters.js +4 -3
  6. package/dist/adapters/jsonAdapters.js +28 -6
  7. package/dist/auth.d.ts +7 -4
  8. package/dist/auth.js +8 -6
  9. package/dist/core/auth/api.d.ts +29 -1
  10. package/dist/core/auth/api.js +47 -13
  11. package/dist/core/auth/createAuth.d.ts +4 -0
  12. package/dist/core/auth/createAuth.js +3 -3
  13. package/dist/core/auth/credentials.d.ts +3 -0
  14. package/dist/core/auth/credentials.js +1 -1
  15. package/dist/core/auth/csrf.d.ts +25 -1
  16. package/dist/core/auth/csrf.js +29 -4
  17. package/dist/core/auth/renewal.d.ts +26 -0
  18. package/dist/core/auth/renewal.js +24 -0
  19. package/dist/core/auth/routes.js +8 -6
  20. package/dist/core/auth/session.d.ts +45 -0
  21. package/dist/core/auth/session.js +47 -1
  22. package/dist/core/auth/sessionActivity.d.ts +11 -0
  23. package/dist/core/auth/sessionActivity.js +14 -0
  24. package/dist/core/auth/throttle.d.ts +15 -0
  25. package/dist/core/auth/throttle.js +29 -1
  26. package/dist/core/auth/totp.d.ts +9 -1
  27. package/dist/core/auth/totp.js +11 -6
  28. package/dist/core/createServer.actions.test.d.ts +1 -0
  29. package/dist/core/createServer.d.ts +14 -1
  30. package/dist/core/createServer.functions.test.d.ts +1 -0
  31. package/dist/core/createServer.js +22 -12
  32. package/dist/core/fontStore.js +1 -1
  33. package/dist/core/handlers/authRouteHandlers.d.ts +1 -1
  34. package/dist/core/handlers/authRouteHandlers.js +57 -21
  35. package/dist/core/handlers/csrfMiddleware.d.ts +23 -0
  36. package/dist/core/handlers/csrfMiddleware.js +31 -7
  37. package/dist/core/handlers/csrfMiddleware.test.d.ts +1 -0
  38. package/dist/core/handlers/oauthRouteHandlers.js +5 -3
  39. package/dist/core/handlers/socialRouteHandlers.d.ts +1 -1
  40. package/dist/core/handlers/socialRouteHandlers.js +2 -1
  41. package/dist/core/http/dispatcher.d.ts +9 -1
  42. package/dist/core/http/dispatcher.js +38 -11
  43. package/dist/core/http/navigation.d.ts +5 -2
  44. package/dist/core/http/navigation.js +7 -4
  45. package/dist/core/http/socketResponse.d.ts +18 -0
  46. package/dist/core/http/socketResponse.js +59 -0
  47. package/dist/core/http/sse.d.ts +26 -0
  48. package/dist/core/http/sse.js +78 -0
  49. package/dist/core/http/stages/authRoutes.js +3 -1
  50. package/dist/core/http/stages/exchangeRoute.test.d.ts +1 -0
  51. package/dist/core/http/stages/middlewares.d.ts +1 -1
  52. package/dist/core/http/stages/middlewares.js +16 -10
  53. package/dist/core/http/stages/middlewares.test.d.ts +1 -0
  54. package/dist/core/http/stages/oauth.js +2 -2
  55. package/dist/core/http/stages/sessionRenewal.d.ts +16 -0
  56. package/dist/core/http/stages/sessionRenewal.js +81 -0
  57. package/dist/core/http/stages/sessionRenewal.test.d.ts +1 -0
  58. package/dist/core/http/stages/signInRoute.d.ts +2 -0
  59. package/dist/core/http/stages/signInRoute.js +139 -0
  60. package/dist/core/http/stages/signInRoute.test.d.ts +1 -0
  61. package/dist/core/http/types.d.ts +9 -0
  62. package/dist/core/http/webExchange.d.ts +18 -0
  63. package/dist/core/http/webExchange.js +53 -0
  64. package/dist/core/oauth/authorize.js +37 -13
  65. package/dist/core/oauth/consentPage.js +11 -0
  66. package/dist/core/oauth/issueContext.d.ts +14 -0
  67. package/dist/core/oauth/issueContext.js +32 -0
  68. package/dist/core/oauth/records.d.ts +7 -0
  69. package/dist/core/oauth/register.d.ts +2 -0
  70. package/dist/core/oauth/register.js +32 -11
  71. package/dist/core/oauth/register.test.d.ts +1 -0
  72. package/dist/core/oauth/token.js +21 -6
  73. package/dist/core/oauth/token.test.d.ts +1 -0
  74. package/dist/core/requestParser.d.ts +4 -0
  75. package/dist/core/requestParser.js +14 -7
  76. package/dist/core/sdkAssets.js +1 -1
  77. package/dist/core/server/baseServer.d.ts +14 -1
  78. package/dist/core/server/baseServer.js +43 -11
  79. package/dist/core/server/closeOnSignals.d.ts +28 -0
  80. package/dist/core/server/closeOnSignals.js +47 -0
  81. package/dist/core/server/closeOnSignals.test.d.ts +1 -0
  82. package/dist/core/server/drain.d.ts +18 -0
  83. package/dist/core/server/drain.js +62 -0
  84. package/dist/core/server/drain.test.d.ts +1 -0
  85. package/dist/core/server/fleet/channel.d.ts +103 -0
  86. package/dist/core/server/fleet/channel.js +191 -0
  87. package/dist/core/server/fleet/channel.test.d.ts +1 -0
  88. package/dist/core/server/fleet/fixtures/fleetServer.d.ts +1 -0
  89. package/dist/core/server/fleet/fleet.integration.test.d.ts +1 -0
  90. package/dist/core/server/fleet/link.d.ts +23 -0
  91. package/dist/core/server/fleet/link.js +70 -0
  92. package/dist/core/server/fleet/relay.d.ts +11 -0
  93. package/dist/core/server/fleet/relay.js +104 -0
  94. package/dist/core/server/fleet/relay.test.d.ts +1 -0
  95. package/dist/core/server/fleet/role.d.ts +46 -0
  96. package/dist/core/server/fleet/role.js +59 -0
  97. package/dist/core/server/fleet/role.test.d.ts +1 -0
  98. package/dist/core/server/fleet/stores.d.ts +7 -0
  99. package/dist/core/server/fleet/stores.js +19 -0
  100. package/dist/core/server/fleet/supervisor.d.ts +26 -0
  101. package/dist/core/server/fleet/supervisor.js +108 -0
  102. package/dist/core/server/fleet/supervisor.test.d.ts +1 -0
  103. package/dist/core/server/pageServer.js +36 -5
  104. package/dist/core/services/action.js +10 -2
  105. package/dist/core/services/functionRoutes.d.ts +9 -0
  106. package/dist/core/services/functionRoutes.js +47 -0
  107. package/dist/core/services/realtime.d.ts +8 -0
  108. package/dist/core/services/realtime.js +57 -0
  109. package/dist/core/services/registry.js +6 -2
  110. package/dist/core/staticFiles.js +12 -4
  111. package/dist/core/staticFiles.test.d.ts +1 -0
  112. package/dist/core/transports.d.ts +13 -3
  113. package/dist/core/transports.js +14 -5
  114. package/dist/functions-api.d.ts +281 -0
  115. package/dist/functions.d.ts +19 -0
  116. package/dist/functions.js +5 -0
  117. package/dist/functionsRunner.d.ts +15 -0
  118. package/dist/functionsRunner.js +6 -0
  119. package/dist/handlers.d.ts +2 -2
  120. package/dist/handlers.js +2 -2
  121. package/dist/helpers/atomicFile.d.ts +10 -0
  122. package/dist/helpers/atomicFile.js +33 -0
  123. package/dist/helpers/atomicFile.test.d.ts +1 -0
  124. package/dist/helpers/buildResponseHelpers.js +35 -11
  125. package/dist/helpers/buildServerInfo.js +4 -2
  126. package/dist/helpers/buildServerInfo.test.d.ts +1 -0
  127. package/dist/helpers/cache/CompressedFileCache.d.ts +18 -0
  128. package/dist/helpers/cache/CompressedFileCache.js +44 -0
  129. package/dist/helpers/cache/CompressedFileCache.test.d.ts +1 -0
  130. package/dist/helpers/cache/cacheManager.d.ts +1 -1
  131. package/dist/helpers/cache/cacheManager.js +2 -1
  132. package/dist/helpers/cache/cacheManager.test.d.ts +1 -0
  133. package/dist/helpers/cache/index.d.ts +1 -1
  134. package/dist/helpers/cache/keys.d.ts +15 -1
  135. package/dist/helpers/cache/keys.js +47 -5
  136. package/dist/helpers/cache/serverCaches.d.ts +7 -1
  137. package/dist/helpers/compress.d.ts +3 -1
  138. package/dist/helpers/compress.js +11 -3
  139. package/dist/helpers/hydrationPayload.d.ts +11 -0
  140. package/dist/helpers/hydrationPayload.js +51 -0
  141. package/dist/helpers/hydrationPayload.test.d.ts +1 -0
  142. package/dist/helpers/metrics.d.ts +1 -1
  143. package/dist/helpers/metrics.js +3 -2
  144. package/dist/helpers/outboundGuard.d.ts +14 -6
  145. package/dist/helpers/outboundGuard.js +132 -8
  146. package/dist/helpers/paintedCookie.d.ts +8 -0
  147. package/dist/helpers/paintedCookie.js +13 -0
  148. package/dist/helpers/reportReactBuild.d.ts +9 -0
  149. package/dist/helpers/reportReactBuild.js +18 -0
  150. package/dist/helpers/reportReactBuild.test.d.ts +1 -0
  151. package/dist/helpers/serverLog.d.ts +25 -1
  152. package/dist/helpers/serverLog.js +68 -3
  153. package/dist/helpers/serverLog.test.d.ts +1 -0
  154. package/dist/helpers/wsFrame.d.ts +3 -0
  155. package/dist/helpers/wsFrame.js +5 -0
  156. package/dist/index.d.ts +8 -1
  157. package/dist/index.js +6 -2
  158. package/dist/kernel.d.ts +2 -1
  159. package/dist/kernel.js +3 -2
  160. package/dist/modules/actions/connectorWrite.js +2 -1
  161. package/dist/modules/actions/index.d.ts +41 -13
  162. package/dist/modules/actions/index.js +52 -9
  163. package/dist/modules/actions/jobs/failureStreak.d.ts +17 -0
  164. package/dist/modules/actions/jobs/failureStreak.js +47 -0
  165. package/dist/modules/actions/jobs/failureStreak.test.d.ts +1 -0
  166. package/dist/modules/actions/jobs/index.d.ts +33 -0
  167. package/dist/modules/actions/jobs/index.js +59 -0
  168. package/dist/modules/actions/jobs/memoryQueue.d.ts +33 -0
  169. package/dist/modules/actions/jobs/memoryQueue.js +194 -0
  170. package/dist/modules/actions/jobs/memoryStores.test.d.ts +1 -0
  171. package/dist/modules/actions/jobs/scheduler.d.ts +64 -0
  172. package/dist/modules/actions/jobs/scheduler.js +130 -0
  173. package/dist/modules/actions/jobs/scheduler.test.d.ts +1 -0
  174. package/dist/modules/actions/jobs/schedules.d.ts +23 -0
  175. package/dist/modules/actions/jobs/schedules.js +47 -0
  176. package/dist/modules/actions/jobs/storeClock.d.ts +16 -0
  177. package/dist/modules/actions/jobs/storeClock.js +32 -0
  178. package/dist/modules/actions/jobs/testing/jobQueueContract.d.ts +25 -0
  179. package/dist/modules/actions/jobs/worker.d.ts +64 -0
  180. package/dist/modules/actions/jobs/worker.js +267 -0
  181. package/dist/modules/actions/jobs/worker.test.d.ts +1 -0
  182. package/dist/modules/actions/moduleFor.js +6 -2
  183. package/dist/modules/actions/runtime/errors.d.ts +12 -0
  184. package/dist/modules/actions/runtime/errors.js +16 -1
  185. package/dist/modules/actions/runtime/kvChange.d.ts +27 -0
  186. package/dist/modules/actions/runtime/kvChange.js +23 -0
  187. package/dist/modules/actions/runtime/kvList.d.ts +51 -0
  188. package/dist/modules/actions/runtime/kvList.js +52 -0
  189. package/dist/modules/actions/runtime/kvStore.d.ts +8 -0
  190. package/dist/modules/actions/runtime/kvStore.js +46 -20
  191. package/dist/modules/actions/runtime/memoryKv.d.ts +3 -0
  192. package/dist/modules/actions/runtime/memoryKv.js +19 -1
  193. package/dist/modules/actions/runtime/namespaceKv.d.ts +4 -6
  194. package/dist/modules/actions/runtime/namespaceKv.js +15 -3
  195. package/dist/modules/actions/runtime/namespaceKv.test.d.ts +1 -0
  196. package/dist/modules/actions/runtime/precheck.js +5 -9
  197. package/dist/modules/actions/runtime/rateLimit.d.ts +19 -0
  198. package/dist/modules/actions/runtime/rateLimit.js +19 -0
  199. package/dist/modules/actions/runtime/redisKv.d.ts +27 -0
  200. package/dist/modules/actions/runtime/redisKv.integration.test.d.ts +1 -0
  201. package/dist/modules/actions/runtime/redisKv.js +46 -0
  202. package/dist/modules/actions/runtime/renderResolver.js +2 -2
  203. package/dist/modules/actions/runtime/report.js +2 -1
  204. package/dist/modules/actions/runtime/runAction.d.ts +10 -2
  205. package/dist/modules/actions/runtime/runAction.js +52 -116
  206. package/dist/modules/actions/runtime/runLogger.d.ts +5 -0
  207. package/dist/modules/actions/runtime/runLogger.js +8 -2
  208. package/dist/modules/actions/runtime/signing.d.ts +20 -0
  209. package/dist/modules/actions/runtime/signing.js +25 -0
  210. package/dist/modules/actions/runtime/signing.test.d.ts +1 -0
  211. package/dist/modules/actions/runtime/taskContext.d.ts +33 -0
  212. package/dist/modules/actions/runtime/taskContext.js +124 -0
  213. package/dist/modules/actions/tasks/builtins.js +5 -1
  214. package/dist/modules/actions/tasks/flow.js +81 -10
  215. package/dist/modules/actions/tasks/helpers.js +2 -2
  216. package/dist/modules/actions/tasks/http.js +2 -3
  217. package/dist/modules/actions/tasks/kv.js +39 -0
  218. package/dist/modules/actions/tasks/list.d.ts +2 -0
  219. package/dist/modules/actions/tasks/list.js +145 -0
  220. package/dist/modules/actions/tasks/realtime.d.ts +2 -0
  221. package/dist/modules/actions/tasks/realtime.js +104 -0
  222. package/dist/modules/actions/tasks/registry.d.ts +10 -1
  223. package/dist/modules/actions/tasks/registry.js +17 -6
  224. package/dist/modules/actions/transport/callHandler.js +6 -3
  225. package/dist/modules/actions/transport/catalogHandler.d.ts +5 -2
  226. package/dist/modules/actions/transport/catalogHandler.js +3 -2
  227. package/dist/modules/actions/transport/stream.d.ts +4 -5
  228. package/dist/modules/actions/transport/stream.js +12 -76
  229. package/dist/modules/actions/transport/webhookHandler.js +5 -4
  230. package/dist/modules/actions/types.d.ts +162 -4
  231. package/dist/modules/connectors/engine.js +3 -5
  232. package/dist/modules/functions/build.d.ts +22 -0
  233. package/dist/modules/functions/build.js +122 -0
  234. package/dist/modules/functions/build.test.d.ts +1 -0
  235. package/dist/modules/functions/capabilities.d.ts +22 -0
  236. package/dist/modules/functions/capabilities.js +228 -0
  237. package/dist/modules/functions/config.d.ts +34 -0
  238. package/dist/modules/functions/config.js +24 -0
  239. package/dist/modules/functions/context.d.ts +17 -0
  240. package/dist/modules/functions/context.js +88 -0
  241. package/dist/modules/functions/contract.d.ts +121 -0
  242. package/dist/modules/functions/contract.js +9 -0
  243. package/dist/modules/functions/driver.d.ts +36 -0
  244. package/dist/modules/functions/driver.js +150 -0
  245. package/dist/modules/functions/load.d.ts +15 -0
  246. package/dist/modules/functions/load.js +45 -0
  247. package/dist/modules/functions/load.test.d.ts +1 -0
  248. package/dist/modules/functions/local.d.ts +29 -0
  249. package/dist/modules/functions/local.js +61 -0
  250. package/dist/modules/functions/local.test.d.ts +1 -0
  251. package/dist/modules/functions/manifest.d.ts +24 -0
  252. package/dist/modules/functions/manifest.js +163 -0
  253. package/dist/modules/functions/manifest.test.d.ts +1 -0
  254. package/dist/modules/functions/native.d.ts +8 -0
  255. package/dist/modules/functions/native.js +21 -0
  256. package/dist/modules/functions/native.test.d.ts +1 -0
  257. package/dist/modules/functions/protocol.d.ts +216 -0
  258. package/dist/modules/functions/protocol.js +18 -0
  259. package/dist/modules/functions/routes.d.ts +36 -0
  260. package/dist/modules/functions/routes.js +181 -0
  261. package/dist/modules/functions/routes.test.d.ts +1 -0
  262. package/dist/modules/functions/runner/messages.d.ts +5 -0
  263. package/dist/modules/functions/runner/messages.js +188 -0
  264. package/dist/modules/functions/runner/remote.d.ts +19 -0
  265. package/dist/modules/functions/runner/remote.js +116 -0
  266. package/dist/modules/functions/runner/remote.test.d.ts +1 -0
  267. package/dist/modules/functions/runner/service.d.ts +32 -0
  268. package/dist/modules/functions/runner/service.js +209 -0
  269. package/dist/modules/functions/sandbox/guest.d.ts +45 -0
  270. package/dist/modules/functions/sandbox/guest.js +660 -0
  271. package/dist/modules/functions/sandbox/isolate.d.ts +37 -0
  272. package/dist/modules/functions/sandbox/isolate.js +347 -0
  273. package/dist/modules/functions/sandbox/isolate.test.d.ts +1 -0
  274. package/dist/modules/functions/sandbox/prelude.d.ts +2 -0
  275. package/dist/modules/functions/sandbox/prelude.js +49 -0
  276. package/dist/modules/functions/space.d.ts +40 -0
  277. package/dist/modules/functions/space.js +162 -0
  278. package/dist/modules/functions/testing/tasksOf.d.ts +4 -0
  279. package/dist/modules/functions/tryEntry.d.ts +9 -0
  280. package/dist/modules/functions/tryEntry.js +49 -0
  281. package/dist/modules/mongo/index.d.ts +4 -0
  282. package/dist/modules/mongo/jobQueue.d.ts +38 -0
  283. package/dist/modules/mongo/jobQueue.js +335 -0
  284. package/dist/modules/mongo/kv.d.ts +15 -0
  285. package/dist/modules/mongo/kv.js +129 -0
  286. package/dist/modules/mongo/stores.integration.test.d.ts +1 -0
  287. package/dist/modules/mysql/accounts.js +19 -9
  288. package/dist/modules/mysql/index.d.ts +2 -1
  289. package/dist/modules/mysql/jobs/index.d.ts +5 -0
  290. package/dist/modules/mysql/jobs/jobQueue.d.ts +40 -0
  291. package/dist/modules/mysql/jobs/jobQueue.js +375 -0
  292. package/dist/modules/mysql/jobs/kv.d.ts +18 -0
  293. package/dist/modules/mysql/jobs/kv.js +108 -0
  294. package/dist/modules/mysql/jobs/schema.d.ts +33 -0
  295. package/dist/modules/mysql/jobs/schema.js +113 -0
  296. package/dist/modules/mysql/jobs/stores.integration.test.d.ts +1 -0
  297. package/dist/modules/mysql/schema.js +25 -0
  298. package/dist/modules/mysql/sessions.d.ts +1 -11
  299. package/dist/modules/mysql/sessions.js +6 -1
  300. package/dist/modules/realtime/declarations.d.ts +11 -0
  301. package/dist/modules/realtime/declarations.js +26 -0
  302. package/dist/modules/realtime/grants.d.ts +23 -0
  303. package/dist/modules/realtime/grants.js +61 -0
  304. package/dist/modules/realtime/handlers.d.ts +74 -0
  305. package/dist/modules/realtime/handlers.js +224 -0
  306. package/dist/modules/realtime/hub.d.ts +62 -0
  307. package/dist/modules/realtime/hub.js +141 -0
  308. package/dist/modules/realtime/index.d.ts +51 -0
  309. package/dist/modules/realtime/index.js +62 -0
  310. package/dist/modules/realtime/memoryPubSub.d.ts +10 -0
  311. package/dist/modules/realtime/memoryPubSub.js +43 -0
  312. package/dist/modules/realtime/realtime.test.d.ts +1 -0
  313. package/dist/modules/realtime/redisPubSub.d.ts +24 -0
  314. package/dist/modules/realtime/redisPubSub.js +36 -0
  315. package/dist/modules/realtime/redisPubSub.test.d.ts +1 -0
  316. package/dist/modules/realtime/socket.d.ts +25 -0
  317. package/dist/modules/realtime/socket.js +122 -0
  318. package/dist/modules/realtime/socket.test.d.ts +1 -0
  319. package/dist/modules/rsc/handler.d.ts +16 -0
  320. package/dist/modules/rsc/handler.js +17 -10
  321. package/dist/modules/rsc/handler.test.d.ts +1 -0
  322. package/dist/modules/rsc/resolveRscData.js +2 -1
  323. package/dist/modules/runtime/bundle.d.ts +24 -0
  324. package/dist/modules/runtime/bundle.js +102 -0
  325. package/dist/modules/runtime/contract.d.ts +37 -0
  326. package/dist/modules/runtime/contract.js +21 -0
  327. package/dist/modules/runtime/entry.test.d.ts +1 -0
  328. package/dist/modules/runtime/host.d.ts +38 -0
  329. package/dist/modules/runtime/host.js +68 -0
  330. package/dist/modules/runtime/inside.d.ts +15 -0
  331. package/dist/modules/runtime/inside.js +47 -0
  332. package/dist/modules/runtime/inside.test.d.ts +1 -0
  333. package/dist/modules/runtime/runner.d.ts +11 -0
  334. package/dist/modules/runtime/runner.js +38 -0
  335. package/dist/modules/runtime/runtime.test.d.ts +1 -0
  336. package/dist/modules/runtime/stages.d.ts +50 -0
  337. package/dist/modules/runtime/stages.js +117 -0
  338. package/dist/modules/ssr/Component.d.ts +4 -5
  339. package/dist/modules/ssr/Component.js +2 -2
  340. package/dist/modules/ssr/handler.js +10 -2
  341. package/dist/modules/ssr/loadPluginComponents.js +2 -1
  342. package/dist/modules/ssr/prepareRender.js +39 -13
  343. package/dist/modules/ssr/preview.d.ts +3 -0
  344. package/dist/modules/ssr/preview.js +17 -7
  345. package/dist/modules/ssr/redirect.test.d.ts +1 -0
  346. package/dist/modules/ssr/registerExternalPlugins.d.ts +6 -1
  347. package/dist/modules/ssr/registerExternalPlugins.js +19 -16
  348. package/dist/modules/ssr/registerExternalPlugins.test.d.ts +1 -0
  349. package/dist/modules/ssr/reportMissingPlugins.d.ts +9 -0
  350. package/dist/modules/ssr/reportMissingPlugins.js +24 -0
  351. package/dist/modules/ssr/reportMissingPlugins.test.d.ts +1 -0
  352. package/dist/modules/ssr/streamBody.d.ts +2 -2
  353. package/dist/modules/ssr/streamBody.js +4 -1
  354. package/dist/modules/ssr/template.js +1 -1
  355. package/dist/modules/ssr/views/template.ejs +8 -5
  356. package/dist/mongo.d.ts +25 -0
  357. package/dist/mongo.js +3 -0
  358. package/dist/mysql.d.ts +11 -0
  359. package/dist/mysql.js +4 -1
  360. package/dist/oauth.js +2 -2
  361. package/dist/plugins/compile.js +9 -4
  362. package/dist/plugins/copy.js +3 -3
  363. package/dist/plugins/manager.d.ts +35 -3
  364. package/dist/plugins/manager.js +109 -38
  365. package/dist/runtime.d.ts +15 -0
  366. package/dist/runtime.js +7 -0
  367. package/package.json +52 -11
  368. package/skills/plitzi-authoring/SKILL.md +153 -263
  369. package/skills/plitzi-authoring/reference/accessibility.md +111 -0
  370. package/skills/plitzi-authoring/reference/authoring-errors.md +70 -0
  371. package/skills/plitzi-authoring/reference/data-and-visibility.md +180 -0
  372. package/skills/plitzi-authoring/reference/elements-and-styles.md +161 -0
  373. package/skills/plitzi-authoring/reference/flows.md +171 -0
  374. package/skills/plitzi-authoring/reference/layouts.md +140 -0
  375. package/skills/plitzi-authoring/reference/lists.md +74 -0
  376. package/skills/plitzi-authoring/reference/performance.md +67 -0
  377. package/skills/plitzi-authoring/reference/plugins.md +167 -0
  378. package/skills/plitzi-authoring/reference/review-checklist.md +67 -0
  379. package/skills/plitzi-authoring/reference/structure.md +83 -0
  380. package/skills/plitzi-authoring/reference/templates-and-export.md +54 -0
  381. package/skills/plitzi-authoring/reference/templates.md +109 -0
  382. package/skills/plitzi-authoring/reference/testing.md +119 -0
  383. package/skills/plitzi-authoring/reference/validation.md +73 -0
  384. package/dist/modules/actions/runtime/schedule.d.ts +0 -35
  385. package/dist/modules/actions/runtime/schedule.js +0 -76
  386. /package/dist/{modules/actions/runtime/schedule.test.d.ts → adapters/jsonAdapters.test.d.ts} +0 -0
@@ -0,0 +1,74 @@
1
+ # Lists
2
+
3
+ A `list` renders its children once per item. Its rows come from its `items` — an array written or bound on the list:
4
+ a provider's answer, `state`, a fixed set. **Only `source: 'controlled'` reads them.** The default, `'none'`, renders
5
+ its children once as a plain `<ul>` and never looks at `items`, so a list with items and any other source is refused
6
+ (`list-items-ignored`). A CMS collection arrives the same way: a provider with its `connector` around the list, and the
7
+ list bound to its records (see the provider in data-and-visibility).
8
+
9
+ ```ts
10
+ list({
11
+ id: 'games',
12
+ source: 'controlled',
13
+ class: gameGrid,
14
+ bind: { items: 'catalog.data.games' }, // an array from the provider around it
15
+ children: [gameCard()] // rendered once per game
16
+ })
17
+ ```
18
+
19
+ - Each row publishes **`list_<id>.item`** (the row) and **`list_<id>.index`** (its position, as text). Inside the row,
20
+ bindings read the short form (`'games.item.title'`), templates and attribute tokens the full one
21
+ (`{{ list_games.item.slug }}`).
22
+ - A **nested list** sees the outer row: inside `list_features`, `list_games.item` is still the game.
23
+ - The list is a `<ul>` and its `class` styles that root; each child is rendered straight into it, with no wrapper.
24
+ A card grid needs `{ margin: '0px', padding: '0px', 'list-style-type': 'none' }` in its class.
25
+ - Fixed data is `items: [ … ]` on the list itself — no provider needed.
26
+ - Everything inside a row is `repeated` in the handles: a test addresses one copy with `.first()` / `.nth()`.
27
+
28
+ ## Filtering and sorting
29
+
30
+ Bind `items` through a template that hands over its **value**:
31
+
32
+ ```ts
33
+ list({
34
+ id: 'shown',
35
+ source: 'controlled',
36
+ bind: [bindTemplate('items', 'catalog.data.games',
37
+ "{{ source|filter(g => (state.genre ?? '') == '' or g.genre == state.genre)|sort((a, b) => b.score - a.score) }}",
38
+ { returns: 'value' })],
39
+ children: [gameCard()]
40
+ })
41
+ ```
42
+
43
+ Only the rows that match are rendered. A counter and an empty state read the same predicate over the same source:
44
+ `{{ source|filter(…)|length }}` as text, and `visible: { source: 'catalog.data.games', template: "{{ source is defined
45
+ and source|filter(…)|length == 0 }}" }`.
46
+
47
+ Hiding rows one by one with a visibility binding also works, but every hidden row stays in the DOM (with its whole
48
+ subtree unless the row says `loadStrategy: 'visible'`). Filter the items instead.
49
+
50
+ ## A detail page
51
+
52
+ A page with a route param (`slug: 'games/{{slug}}'`) shows one record. With a server provider, `singleRecord` and
53
+ `filters` fetch just that one. In the browser — an offline project, a `query` returning the whole collection — narrow
54
+ the list to the record the route names:
55
+
56
+ ```ts
57
+ apiContainer({ id: 'catalog', query: '/data/games.json', cache: true, children: [
58
+ list({
59
+ id: 'game',
60
+ source: 'controlled',
61
+ bind: [bindTemplate('items', 'catalog.data.games',
62
+ '{{ source|filter(g => g.slug == navigation.routeParams.slug) }}', { returns: 'value' })],
63
+ children: [gameDossier()] // reads list_game.item
64
+ }),
65
+ text('Signal lost — no game by that name.', {
66
+ id: 'game-missing',
67
+ visible: { source: 'catalog.data.games',
68
+ template: "{{ source is defined and not (source|find('slug', navigation.routeParams.slug)) }}" }
69
+ })
70
+ ] })
71
+ ```
72
+
73
+ A link to it from a card is an attribute token read inside the card's row:
74
+ `link({ mode: 'internal', href: '/games/{{ list_games.item.slug }}' })`.
@@ -0,0 +1,67 @@
1
+ # Performance: what renders, and what it costs
2
+
3
+ A page that does the right thing can still do it by rendering everything, and nothing shows it but the frame rate — on
4
+ somebody else's slower machine. Measure it (`inspectRenders`, below) instead of guessing, and write the page so that a
5
+ change renders what shows it.
6
+
7
+ ## What makes an element render
8
+
9
+ An element renders again when something it READS changes, and only then:
10
+
11
+ - **A binding, a `when` rule or an attribute template subscribes to each path it names** — `computed.tool`, not all of
12
+ `computed`; `state.cart.count`, not all of `state`. Name what you show: a template that reads `state.cart` renders on
13
+ every change to anything in the cart.
14
+ - **A computed value is evaluated again whenever the state, the variables, the route, the session, the host or the
15
+ theme changes.** A value that comes out the same keeps its object, so what reads it does not render — but the work of
16
+ evaluating it is done every time. Keep computed values cheap: a filter over a list of five thousand rows belongs in
17
+ the binding of the list that shows them (`bindTemplate(…, { returns: 'value' })`) or in the provider, where it runs
18
+ when the list changes and not on every keystroke anywhere on the page.
19
+ - **A parent rendering does not render its children** unless it hands them something new. What an element's own
20
+ bindings change is its own.
21
+
22
+ ## What a flow costs
23
+
24
+ - **Synchronous steps are one change.** A `setState` after a `setState` after a `toggleState` renders once, when the
25
+ flow gives the page back — at its end, or at the first step that waits (a request, a `delay`), which renders what
26
+ came before it.
27
+ - **A trigger that fires on every pointer move or keystroke is a flow per event.** For a stream of events, keep the
28
+ moment-to-moment state inside a plugin and hand the space the outcome (`onCommit`, `onSelectionChange`), not every
29
+ step of it.
30
+ - **`whileRunning`** decides what a trigger fired again does while its flow runs: `skip` (the default) drops it,
31
+ `queue` runs it after, `parallel` alongside. A burst of events queued behind a slow request is a burst of renders
32
+ later.
33
+
34
+ ## What mounts
35
+
36
+ - **A hidden element stays mounted** with its whole subtree — a modal's form, a closed panel's lists. `loadStrategy:
37
+ 'visible'` mounts the contents only while it is shown, `'lazy'` from the first time it is shown. Use it for heavy
38
+ subtrees that start hidden.
39
+ - **Every list row is a subtree.** Filter the items the list is given; a row hidden by a condition is still a row.
40
+
41
+ ## Measuring it: `inspectRenders`
42
+
43
+ Render the space with `debugMode` (the SDK profiles every element then), and ask what an interaction rendered:
44
+
45
+ ```ts
46
+ import { inspectRenders } from '@plitzi/sdk-authoring';
47
+
48
+ const report = await inspectRenders(page, () => page.click('[data-plitzi-el="save"]'), { max: 10 });
49
+ expect(report.problems).toEqual([]);
50
+ ```
51
+
52
+ `report.elements` lists every element that rendered, how often, and what changed for it (`attributes`, `elementState`,
53
+ a prop's name); `report.causes` the store paths written meanwhile. `max` turns it into an assertion: over it,
54
+ `problems` names the elements that rendered most. A test that holds an interaction to its budget is how a regression
55
+ is caught before anybody feels it.
56
+
57
+ ## When something is slow: where to look
58
+
59
+ 1. **Count before changing anything.** `inspectRenders` around the interaction that feels slow. Hundreds of elements
60
+ for one click is the problem; three is not, and the cost is somewhere else (a plugin, a large list mounting).
61
+ 2. **Read `changed` for the elements that rendered most.** Their own `attributes` or `elementState` changed: find what
62
+ they read in `causes` — a template naming more than it shows, a computed value that reads it. `nothing of its own`:
63
+ its context or its parent carried it; look at the element above it that did change.
64
+ 3. **Read `causes`.** A path you did not expect written — a flow writing on every pointer move, a provider refetching —
65
+ is the change to stop at its source.
66
+ 4. **Only then the browser's profiler**, for what a render costs rather than how many there are: a plugin drawing, a
67
+ list of thousands mounting at once.
@@ -0,0 +1,167 @@
1
+ # Plugins
2
+
3
+ A plugin is a React component of the project's own — a chart, a map, a game — hosted in the space by a `custom`
4
+ element that names it by `renderType`:
5
+
6
+ ```ts
7
+ custom({ id: 'arcade', renderType: 'nebulaRun', shipColor: 'amber', bind: { best: 'state.arcadeBest' } })
8
+ ```
9
+
10
+ ## Creating one
11
+
12
+ The CLI writes it, in the shape Plitzi's own elements are written in:
13
+
14
+ ```bash
15
+ npx @plitzi/cli add plugin seat-picker legend # elements of this project (src/plugins/<Name> in a CLI project)
16
+ npx @plitzi/cli create seat-picker --plugin # a package of its own, with a preview
17
+ npx @plitzi/cli pack plugin # built, with its manifest and the zip the builder takes
18
+ ```
19
+
20
+ One folder, four files: `SeatPicker.tsx` (the component), `declaration.ts` (its `type`, the `triggers` it fires, the
21
+ `callbacks` it answers to, and the element the builder adds — data only), `Settings.tsx` (its panel in the builder), and
22
+ `index.ts` (`Object.assign(Component, declaration, { pluginSettings: Settings })`). An event or an action is declared in
23
+ `declaration.ts` and registered by the component from there, never only in the component.
24
+
25
+ A plugin fires its events with `interactionsManager.interactionTrigger(id, action, payload)` whenever it has news —
26
+ from its first effect too: a flow starts once the page has finished mounting, so the `setState` it calls is there. No
27
+ `queueMicrotask` or delay of your own. An event that REPORTS a state, and may fire again before its flow ends (as a
28
+ component reads its real value right after mounting), wants `whileRunning('queue', …)` on the flow: by default a firing
29
+ while the flow runs is dropped, and the stale first report would stick.
30
+
31
+ ## Props
32
+
33
+ **The host element's attributes ARE the component's props.** Whatever the space writes on the `custom` element arrives
34
+ by the same name, and so does whatever a binding writes — which is what makes a plugin live: bind an attribute to
35
+ `state`, a provider or a row, and the component re-renders with the answer. Prefer this to anything else.
36
+
37
+ `settings` is the other channel: a JSON string whose keys are merged into the props too. It is what the builder's
38
+ panel edits for a plugin it has no controls for; from code, write attributes. A `settings` that is not JSON renders
39
+ "Settings Malformed" in the page.
40
+
41
+ Every prop is optional and has a default: an attribute not authored yet, or a binding whose source has not answered,
42
+ is `undefined`, and a plugin that renders nothing then is a hole in the page.
43
+
44
+ ## The component
45
+
46
+ ```tsx
47
+ import { RootElement } from '@plitzi/plitzi-sdk';
48
+
49
+ const NebulaRun = ({ shipColor = 'amber', best = 0, className }: NebulaRunProps) => (
50
+ <RootElement className={className}>…</RootElement>
51
+ );
52
+ ```
53
+
54
+ - **Render `RootElement`**, not a `div`: it carries the element's id, classes and `data-plitzi-el`, so the CSS
55
+ authored on the element applies, the builder can select it and a test can find it.
56
+ - **Render the same thing on the server and on the first client pass.** A clock, a random number or anything read from
57
+ `window` belongs in an effect; a hydration mismatch discards the whole tree, not the plugin.
58
+ - **Borrow colours.** `currentColor` and the space's `var(--…)` tokens follow the theme; a hard-coded colour is
59
+ invisible in one of them. A canvas reads them with `getComputedStyle(element).getPropertyValue('--accent')` and
60
+ again when `theme.resolved` changes (bind it as a prop).
61
+
62
+ ## Writing to the space
63
+
64
+ A plugin that produces something the page shows — a score, a selection — writes it to `runtime.state`, where every
65
+ binding reads it:
66
+
67
+ ```ts
68
+ import { getStateManager } from '@plitzi/plitzi-sdk';
69
+
70
+ const state = getStateManager();
71
+ state.setStateByKey('arcadeBest', Math.max(score, Number(state.state.arcadeBest ?? 0)));
72
+ const stop = state.subscribe(next => save(next)); // called after every change; call `stop()` to leave
73
+ ```
74
+
75
+ `state` is the current value, `setState` replaces it (or takes an updater), `setStateByKey` writes one key,
76
+ `clearState` empties it, `subscribe` listens. Bindings on `state.*` re-render at once.
77
+
78
+ ## Talking to other pages
79
+
80
+ A plugin that moves at the speed of a cursor reads a realtime channel through `useChannel` rather than through flows —
81
+ messages arrive through a callback and re-render nothing:
82
+
83
+ ```tsx
84
+ import { useChannel } from '@plitzi/plitzi-sdk';
85
+
86
+ const room = useChannel(roomTopic, { onMessage: message => move(message.from, message.data) });
87
+ room.publish('pointer', { x, y }); // throttle it: ~20 a second, the latest position each time
88
+ room.members; // who is here, with the state each announced
89
+ ```
90
+
91
+ Take the topic as a prop (`bindTemplate('roomTopic', 'board.id', 'room:{{ source }}')`) so the space names its
92
+ channels, and trust a message you act on only when `message.from === 'server'`. The page has one connection and is one
93
+ member per topic, however many elements and plugins listen.
94
+
95
+ ## Registering
96
+
97
+ A project `plitzi create` wrote registers every folder of `src/plugins` by itself, under the folder's name in
98
+ camelCase (`StatCard` → `statCard`). Anywhere else, the entry registers the component under its `renderType`: the
99
+ third argument to `render()`, `<PlitziSdk.Plugin>` in a React application, `plugins` on a page server of your own. A `renderType` nothing registered
100
+ renders "Custom Component … Not Found", and a page server logs the missing `renderType` at `error` once per
101
+ space: on a server, register it in `plugins` AND name it in the deployment's `pluginNames`.
102
+
103
+ A plugin PACKAGE, loaded by a space from its `plugin-manifest.json`, is an element TYPE of its own rather than a
104
+ `custom` host — it is how the builder adds one somebody dropped. Author it with a typed factory:
105
+ `defineElement<SeatPickerAttributes>(declaration)` from the plugin's own `declaration.ts`, or
106
+ `elementsFromManifest<{ seatPicker: SeatPickerAttributes }>(manifest)` from what it published — or untyped,
107
+ `element('seatPicker', { id: 'seats', start: 3 })`.
108
+
109
+ ## Checked like a built-in element
110
+
111
+ Hand `authorSpace` the plugin's DECLARATION — the `declaration.ts` beside the component, or a manifest's
112
+ `pluginSchema` entry:
113
+
114
+ ```ts
115
+ import declaration from './plugins/SeatPicker/declaration';
116
+
117
+ authorSpace(space, { plugins: [declaration] });
118
+ ```
119
+
120
+ It is then held to what it declares, whether it is authored as its own type or hosted by `custom({ renderType:
121
+ 'seatPicker' })`: a flow on an event it never fires, a step sent to an action it does not answer, an attribute it does
122
+ not read — each is refused with the name it should have been. (`pluginTypes: ['seatPicker']` only tells the linter the
123
+ type exists; nothing about how the space uses it is checked.) A `custom` host whose component is NOT handed over is not
124
+ judged on its events at all — nothing here knows them.
125
+
126
+ Its events and actions have builders typed from the same declaration, so a name it does not declare is a compile error:
127
+
128
+ ```ts
129
+ seats({ id: 'seats', flows: [[named('picked', declaredTrigger(declaration, 'onPick')), setState({ key: 'seat', type: 'text', value: '{{ picked.seat }}' })]] })
130
+ button({ content: 'Clear', flows: [[onClick(), declaredCallback(declaration, 'reset', { on: 'seats' })]] })
131
+ ```
132
+
133
+ A plugin says what HAPPENED through its events (`onPick`, with the seat in the payload) and lets the space's flows
134
+ decide what that means — write `state`, open a modal, call a server action. Prefer that to writing `state` from inside
135
+ the component: the flow is visible in the space, the builder shows it, and the same act can come from a button too.
136
+
137
+ ## Components that draw into DOM they do not render
138
+
139
+ A map, a chart library, anything that positions its own markers or popups: its roots are the library's to place. Never
140
+ give them a class that sets `position` — the element falls into the page's flow, offset by every marker before it. Put
141
+ the look in the plugin's own stylesheet (imported CSS ships beside the bundle) and take colours from custom properties
142
+ the space sets (`--seat-accent: var(--accent)` in `customCss`); a canvas or WebGL layer resolves them through a probe
143
+ element with `getComputedStyle(probe).color`, again whenever `theme.resolved` changes (bind it as a prop).
144
+
145
+ ## Components that draw a lot
146
+
147
+ A canvas with thousands of shapes on it — a whiteboard, a diagram, a map of points — is judged on a slower machine than
148
+ yours: sixty frames a second here says nothing about a four-year-old laptop. What keeps one fast is doing work in
149
+ proportion to what CHANGED, not to what is there:
150
+
151
+ - **Two layers.** Paint what stands still on one canvas and leave it; clear and draw the one over it every frame, with
152
+ only what moves: the cursor, the selection, what is being dragged.
153
+ - **Repaint the part that changed.** Something added, edited or removed repaints the area it was and is in, clipped,
154
+ with whatever reaches into it — not the whole board. A pan moves the picture already painted by whole device pixels
155
+ and paints the edges it uncovers.
156
+ - **Draw a dragged group once.** Everything picked up moves by the same amount at every step: draw it to a canvas of its
157
+ own when the drag begins and copy that into place after.
158
+ - **Cache per object, not per frame.** Treat elements as immutable and key what is derived from one — its bounds, its
159
+ shape, a resolved connector — on the object itself (a `WeakMap`), so nothing is worked out twice for the same thing.
160
+ - **Hand the space outcomes, not motion.** A stroke finished, a selection changed: a trigger each. Firing one at every
161
+ pointer move runs a flow and renders the page at every pointer move.
162
+ - **Measure with a bench, throttled.** A script that drives the real thing in a browser with the CPU slowed
163
+ (`Emulation.setCPUThrottlingRate`) and reports script time per frame, not only frames per second — and a test that
164
+ counts WORK (full repaints, strokes drawn during a drag), which holds on any machine where a timing does not.
165
+
166
+ Pizarra, the collaborative whiteboard on the platform (`pizarra.plitzi.app`), does all of it on a board of thousands
167
+ of elements.
@@ -0,0 +1,67 @@
1
+ # Before you say it is done
2
+
3
+ The feedback a reviewer gives on almost every change to a space, so you can give it to yourself first. Go through all
4
+ of it; each line is something that has shipped broken before.
5
+
6
+ ## It renders, and says so
7
+
8
+ - [ ] `authorSpace` returns **zero warnings**. Each one is written code that will not do what it says.
9
+ - [ ] No console errors on any page you touched.
10
+
11
+ ## Nothing flashes, nothing jumps
12
+
13
+ - [ ] Nothing appears only to be taken away while data loads: a condition that REVEALS an element starts hidden and
14
+ answers `'false'` until its source arrives. Checked from the FIRST frame, not a screenshot at the end.
15
+ - [ ] Nothing that is on screen by default disappears because a flag has not been set yet (a sidebar until it is
16
+ folded). Checked on a fresh visit, with no state kept from before.
17
+ - [ ] Empty states show only when the answer arrived and is empty — never while loading.
18
+ - [ ] No hidden element leaves a hole: the element itself is hidden, not a wrapper around it.
19
+ - [ ] Nothing shifts when the data lands: a loading area keeps its size, numbers use tabular figures.
20
+
21
+ ## Every screen, every theme
22
+
23
+ - [ ] Desktop, tablet and mobile — and nothing scrolls sideways on a phone. A rule for tablets and phones alike is
24
+ written under `compact`.
25
+ - [ ] Light AND dark. Every colour is a token with both values; no theme-following text on a fixed background.
26
+ - [ ] Focus is visible on everything clickable (`focus-visible`), and `prefers-reduced-motion` stops what moves.
27
+
28
+ ## Usable without sight
29
+
30
+ Screen readers and browser agents (Claude in Chrome) work a page through its accessibility tree. See
31
+ [accessibility](accessibility.md).
32
+
33
+ - [ ] Every button and link has words — an icon-only button a `title`, a link around a card a `label` — and every field
34
+ a `label` (`hideLabel: true` when the design shows what it is).
35
+ - [ ] Every image has an `alt` saying what it shows, or `decorative: true`.
36
+ - [ ] Every click is on a `button` or a `link`; the whole page can be worked from the keyboard.
37
+ - [ ] A toggle binds `ariaPressed`, a button that opens something `ariaExpanded`.
38
+ - [ ] Headings step down one level at a time; parts of the page a person jumps to are landmarks (`nav`, `main`, a
39
+ labelled `section`).
40
+ - [ ] What a canvas or a plugin draws is also there as elements, with a visible way to reach them.
41
+
42
+ ## No copies
43
+
44
+ - [ ] Chrome shared by pages is a layout; the current menu entry comes from `activeOn`, not per-page styling.
45
+ - [ ] A look used twice is a class; a tree used twice is a function or a `map` over data; a list of pages, links or
46
+ plans is ONE array everything reads.
47
+ - [ ] No positional ids (`container-45`) in what you wrote; every referenced element is named, prefixed when a
48
+ helper runs more than once.
49
+
50
+ ## True, and in the right place
51
+
52
+ - [ ] Every number, name and status on screen comes from a source. No placeholder figures, no invented logos or
53
+ testimonials. A panel with nothing true to show is an empty state.
54
+ - [ ] Times are formatted with an explicit zone and say which (`… 06:00 UTC`).
55
+ - [ ] Logic lives in binding templates or on the server, not in an attribute (where a condition is never evaluated).
56
+ - [ ] A filtered or sorted list binds its `items` to a template that returns its value — its rows are not hidden one
57
+ by one.
58
+ - [ ] Every link you wrote was followed at least once: an `href` built from a row goes where it says.
59
+ - [ ] Writes refresh what they changed — and only that (`invalidateQueries`); a multi-step form does not refresh the
60
+ provider that decides whether to leave.
61
+ - [ ] What only an administrator may read is shown only to administrators — and the check is a real boolean, not
62
+ a template left as text.
63
+
64
+ ## Proven
65
+
66
+ - [ ] You looked at it: the visual check ran, on the pages you changed, in both themes.
67
+ - [ ] Both sides of each condition were seen: the empty account and the full one, the admin and the member.
@@ -0,0 +1,83 @@
1
+ # Structuring a space that grows
2
+
3
+ A one-screen space fits in `src/space.ts`. Past a few pages, split it by what changes together — and keep every
4
+ repeated thing in exactly one place.
5
+
6
+ ```text
7
+ src/space/
8
+ index.ts the SpaceSpec: name, variables, fonts, layouts, pages — assembly only
9
+ tokens.ts colour, shadow and radius variables, each with light and dark
10
+ styles.ts classes shared across areas (buttons, panels, page titles)
11
+ components.ts functions returning ElementSpec: panel(), statCard(), emptyState()
12
+ layouts/app.ts the application shell (+ its own classes)
13
+ docs/
14
+ nav.ts the section's pages as data: id, slug, title, lede, order
15
+ layout.ts the section shell, generated from nav.ts
16
+ page.ts docsPage(id, content) — the shape every page of the section shares
17
+ pages/
18
+ home.ts one file per page, or per page family
19
+ ```
20
+
21
+ ## Components are functions
22
+
23
+ The same block in two places is a function with parameters. Its ids come from a parameter, because ids are one
24
+ namespace for the whole space:
25
+
26
+ ```ts
27
+ export const panel = (params: { id: string; title: string; sub?: string; link?: { href: string; label: string }; children: ElementSpec[] }): ElementSpec =>
28
+ container({
29
+ id: params.id,
30
+ class: panelCard,
31
+ children: [
32
+ container({
33
+ id: `${params.id}-head`,
34
+ class: panelHead,
35
+ children: [
36
+ heading(params.title, { id: `${params.id}-title`, subType: 'h3', class: panelTitle }),
37
+ ...(params.sub ? [text(params.sub, { id: `${params.id}-sub`, class: panelSub })] : []),
38
+ ...(params.link ? [link({ id: `${params.id}-link`, href: params.link.href, class: panelLink, children: [text(params.link.label)] })] : [])
39
+ ]
40
+ }),
41
+ ...params.children
42
+ ]
43
+ });
44
+ ```
45
+
46
+ A page then reads as what it shows: `panel({ id: 'hm-traffic', title: 'Traffic', link: { href: 'analytics', label:
47
+ 'Open analytics →' }, children: [chart] })`.
48
+
49
+ ## Lists are data
50
+
51
+ Four quick links, three pricing plans, a sidebar menu, the steps of an onboarding: an array and a `map`.
52
+
53
+ ```ts
54
+ const QUICK = [
55
+ { id: 'team', icon: 'fas fa-user-plus', title: 'Invite your team', href: 'workspace' },
56
+ { id: 'docs', icon: 'fas fa-book-open', title: 'Quickstart', href: 'docs-quickstart' }
57
+ ] as const;
58
+
59
+ container({ id: 'quick', class: quickGrid, children: QUICK.map(item => quickCard(item)) });
60
+ ```
61
+
62
+ When items differ in ONE detail (one link needs a binding), put that detail in the item's data as an optional field
63
+ — never an `if (item.id === 'team')` inside the map.
64
+
65
+ ## Names
66
+
67
+ - **Ids:** `area-role` in kebab-case — `hm-traffic-title`, `docs-nav-docs-data`. Never the builder's positional
68
+ `container-45` in code you maintain: rename them when you touch the code.
69
+ - **Classes:** named after the role, not the look — `panel-title`, not `bold-16`.
70
+ - **State keys:** flat and descriptive — `docsClosedStart`, `onboardingDetails`.
71
+
72
+ ## Code that came from an export
73
+
74
+ `specFromSpace` writes what the builder saved, faithfully: every element's own `css`, positional ids, `when` rules
75
+ per page. It renders; it is not maintainable. When you work on such a file, leave it better:
76
+
77
+ - repeated `css` blocks → one class;
78
+ - a nav with a hand-styled current entry per page → a layout and `activeOn`;
79
+ - the same tree pasted per item → a function and a `map`;
80
+ - `container-45` → a name.
81
+
82
+ Each of those is safe to do on its own: authoring refuses anything that would not render, and the visual check shows
83
+ the rest.
@@ -0,0 +1,54 @@
1
+ # Templates, exports, and what authoring refuses
2
+
3
+ ## Publishing a template
4
+
5
+ A template is ONE subtree, the style that dresses it and a name, published as a JSON someone drags onto a canvas you
6
+ will never see.
7
+
8
+ ```ts
9
+ import { authorTemplate } from '@plitzi/sdk-authoring';
10
+
11
+ const { template, warnings } = authorTemplate({
12
+ name: 'Pricing card',
13
+ description: 'A price, a list of features and a call to action.',
14
+ classes: { card: { padding: '24px', 'border-radius': '12px', 'background-color': 'var(--surface)' } },
15
+ root: container({ class: 'card', children: [heading('$19', { subType: 'h3' }), button({ content: 'Start' })] })
16
+ });
17
+ ```
18
+
19
+ - **Everything it names, it carries.** A class declared in the space it was cut from does not travel: declare every
20
+ rule the subtree names (`validateTemplate` warns about one it does not carry).
21
+ - **A binding may not point outside the subtree.** Bring the provider into the template, or bind to a global.
22
+
23
+ ## From an exported JSON
24
+
25
+ Never rewrite an exported space by hand. `specFromSpace({ schema, style })` reads it into the spec that authors it
26
+ (listing each repair in `corrections`); `specToSource(spec, { exportName })` writes that spec as code;
27
+ `compareSpaces(original, authorSpace(spec))` must list nothing but what `corrections` explains. The builder's
28
+ **Export** does all three. Edit the code from then on — and tidy it as you go ([structure.md](structure.md)).
29
+
30
+ For a document you did not author — an export, a JSON edited by hand — run the gate before serving it:
31
+ `validateSpace({ schema, style })`.
32
+
33
+ ## What authoring refuses
34
+
35
+ `authorSpace` throws rather than hand back a space that would not render. Each error names the element and the fix:
36
+
37
+ - a CSS property the editor cannot read back; a class or slot the space does not declare (with the likely name)
38
+ - an element with a shared `class` AND rules of its own; one class declared twice with rules that disagree
39
+ - a binding source nothing answers to, or with the wrong prefix
40
+ - a source written by its short name inside a flow's params or a binding's template (`stats.x` for `apiContainer_stats.x`)
41
+ - a name that shadows a global (`variables`, `navigation`, `auth`, `state`, `theme`)
42
+ - two elements with one id — the error says where the first one is
43
+ - a step target that is not there; a broken flow chain, an orphan, a cycle
44
+ - a flow on a trigger its element never fires (`onSubmit` on a button), an element callback sent to a type that
45
+ does not answer it (`openModal` to a container)
46
+ - a `dropdownPopup` outside a `dropdown`, a tab container's parts outside a `tabContainer`
47
+ - a global callback on the wrong module, a utility given one
48
+
49
+ And it WARNS — returned in `warnings`, printed by `npm run author` — for what is written and will not do what it
50
+ says: `unknown-attribute`, `template-never-resolved`, `condition-starts-visible`, `state-key-has-runtime-prefix`,
51
+ `FORM_SUBMIT_UNMANAGED`, `STYLE_WITHOUT_TAG`, `tablet-rule-skips-mobile`. Treat a warning as a bug.
52
+
53
+ Ids are hashes of the path that produced them: re-authoring an unchanged space writes byte-identical documents, so a
54
+ seed can re-run and a diff stays readable.
@@ -0,0 +1,109 @@
1
+ # Templates
2
+
3
+ Plitzi's templates are Twig, run by Plitzi's own interpreter. What a `{{ … }}` does depends on WHERE it is written.
4
+
5
+ ## Where a template runs
6
+
7
+ | Written in | What is evaluated | What it can read |
8
+ | --- | --- | --- |
9
+ | a binding's `twigTemplate` transformer | the whole template: conditions, filters, `{% set %}`, loops, tests | `source` (the bound value), `sourceTo`, the globals, the variables by bare name, and every source around the element |
10
+ | a flow step's params | the whole template | the trigger's payload and earlier steps by name, the globals, sources around the element |
11
+ | an ATTRIBUTE (`href`, `src`, `content`…) | only `{{ name }}` tokens (a filter after the name is allowed) | the globals, the variables and the page's route params by bare name (`{{ apiUrl }}`, `{{ slug }}`), and every source around the element (`{{ list_games.item.slug }}`) |
12
+
13
+ A **query parameter** is `navigation.queryParams.<name>` everywhere — never a bare name.
14
+
15
+ An attribute holding a condition — `href: "/x/{{ on ? 'a' : 'b' }}"` — is used **as written** (`authorSpace` warns
16
+ `template-never-resolved`). Move the expression into a binding with `bindTemplate`:
17
+
18
+ ```ts
19
+ link({ href: '/', mode: 'internal', bind: [bindTemplate('href', 'state.workspace.id', "{{ source ? '/workspace/' ~ source : '/' }}")] })
20
+ ```
21
+
22
+ A value that came from a BINDING is data, never a template: a comment reading `{{ auth.accessToken }}` prints those
23
+ characters. Only what you authored is interpolated.
24
+
25
+ ### A template's value, not its text
26
+
27
+ A binding's template renders TEXT by default. For an attribute that holds a list, a number or a flag — a list's
28
+ `items` above all — hand over the VALUE with `returns: 'value'` (`returnMode: 'value'` on the transformer). The
29
+ template must be a single `{{ expression }}`:
30
+
31
+ ```ts
32
+ list({ id: 'shown', source: 'controlled',
33
+ bind: [bindTemplate('items', 'games.data.games', '{{ source|filter(g => state.genre == "" or g.genre == state.genre) }}', { returns: 'value' })],
34
+ children: [ … ] })
35
+ ```
36
+
37
+ A text template feeding `items` is refused: the list would receive the array written out as JSON and render nothing.
38
+
39
+ ## Naming sources inside a template
40
+
41
+ Always the full name — the element's kind, an underscore, its id: `apiContainer_stats`, `list_rows`,
42
+ `modalContainer_credits`. The globals (`state`, `auth`, `navigation`, `variables`, `host`, `theme`, `computed`) are named
43
+ as themselves. In a flow, an earlier step is read by the name given with `named('quote', …)`: `{{ quote.output.total }}`.
44
+
45
+ An id with a hyphen is one name inside a template — `{{ apiContainer_tn-data.data.total }}` reads that provider. So
46
+ subtraction is written with spaces: `{{ total - used }}`, never `total-used`.
47
+
48
+ A source reaches only the elements **inside** the element that publishes it (a layout's providers reach every page in
49
+ it). A list publishes one scope per row: inside a row, `list_rows.item` is THAT row and `list_rows.index` its position;
50
+ a nested list's rows still see the outer row.
51
+
52
+ `authorSpace` refuses a name nothing answers to, a short name where the full one is needed, and a source read from
53
+ outside the element that publishes it — each with the name it should have been.
54
+
55
+ ## The language
56
+
57
+ - Operators: `~` (concat), `+ - * / // % **`, `== != < > <= >=`, `and or not`, `in`, `starts with`, `ends with`,
58
+ `a ? b : c`, `a ? b` (empty when false), `a ?: b` (a when truthy), `a ?? b` (a when defined).
59
+ - Access: `a.b`, `a.0`, `a[i]`, `(expr).key`, `(expr)[i]`, `['x', 'y'][i]`, `{ k: 1 }.k`.
60
+ - Blocks: `{% set x = … %}`, `{% if %}…{% elseif %}…{% else %}…{% endif %}`, `{% for item in list %}…{% endfor %}`,
61
+ `{% apply upper %}…{% endapply %}`, `{% break %}`, `{% continue %}`.
62
+ - Arrow functions in filters, closing over `{% set %}` variables: `rows|sort(r => r.at)|first`,
63
+ `rows|sort((a, b) => b.score - a.score)`, `rows|filter(r => r.score >= min)`, `rows|map(r => r.name)`.
64
+ - Tests: `is defined`, `is empty`, `is null`, `is iterable`, `is even`, `is odd`, `is same as(x)` (strict: `false` is
65
+ not `'false'`, `0` or an unset value), `is divisible by(n)`, and their `is not` forms. `null` and `none` are literals.
66
+ - Functions: `range`, `min`, `max`, `cycle`.
67
+ - **Not supported**: `matches` (no regular expressions are evaluated — use `starts with`, `ends with`, `in`), macros,
68
+ `include`/`extends`, and any filter or function not listed here. `authorSpace` refuses them, and anything else the
69
+ interpreter would read past, rather than letting a template render a value nobody wrote.
70
+
71
+ Filters: `default upper lower trim capitalize title camelize kebab snake ltrim rtrim pad padRight replace slice split
72
+ join reverse length first last contains startswith endswith number number_format round abs format sort batch chunk map
73
+ reduce merge keys values filter column find pluck unique flatten sum without only index_by group_by url_encode nl2br
74
+ striptags spaceless json_encode to_json object_as_json raw date base64_encode base64_decode md5 random`.
75
+
76
+ `format` is sprintf: `'%02d'|format(n)` → `05`, `'%.1f'|format(x)`, `'%-8s'|format(name)`, `'%+d'|format(delta)`. A bare
77
+ `%f` keeps the number's own digits.
78
+
79
+ A lookup across two sources — a row joined to the stats around it:
80
+
81
+ ```ts
82
+ "{% set s = apiContainer_stats.data.spaces|find('id', list_spaces.item.id) %}{{ s ? s.traffic.views|number_format(0, '.', ',') ~ ' views' : '—' }}"
83
+ // or, in one expression:
84
+ "{{ (apiContainer_stats.data.spaces|find('id', list_spaces.item.id)).traffic.views ?? 0 }}"
85
+ ```
86
+
87
+ ## Values to be careful with
88
+
89
+ - **An empty list is false**, like an absent one. To tell "arrived and empty" from "not arrived":
90
+ `{{ items is defined and items is empty }}`.
91
+ - **`json_encode` prints JSON for any value** — a string quoted and escaped, nothing as `null` — so a JSON document
92
+ is built by encoding each value: `'{ "board": {{ id|json_encode }}, "timer": {{ timer|json_encode }} }'`. In a flow
93
+ step's params and an action's output, `"{{ text }}"` in quotes is safe too — a value inside a string literal of a
94
+ param written as a JSON document is escaped for it. Anywhere else (an attribute, a binding's template) it is not:
95
+ encode.
96
+ - **Numbers**: `number_format(decimals, point, thousands)`; `round(precision)`.
97
+ - **A step param that is one `{{ expression }}` is that value, with its type**: a number stays a number, and a
98
+ string stays a string however it looks — a password typed `1234` arrives as `"1234"`. Nothing is guessed from the
99
+ text: to convert, declare the type where the value lands (`setState`'s `type`, the action's input field). Text around
100
+ the tokens is text, unless it makes a JSON object or array — then it is that document (see `json_encode` above).
101
+
102
+ ## Dates
103
+
104
+ - `|date(format, zone)` — tokens `Y y m n d j H G h g A a i s l D F M N w U`; a backslash prints the next character
105
+ as it is (`\T`). **Always pass a zone** (`'UTC'`) and print it: with no zone the date is formatted in the local time
106
+ of whoever renders it — the server's for the HTML, the visitor's after hydration — and the two disagree.
107
+ - A timestamp can be seconds or milliseconds, a number or a string of digits — all read.
108
+ - For "3 minutes ago", the `dateConverter` transformer with `{ asAge: true }`; give it `isUnix: false` for an ISO
109
+ string. Its checkbox params accept real booleans (and `'true'`/`'false'`).