@svgrid/enterprise 2.0.4 → 2.2.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 (50) hide show
  1. package/README.md +18 -6
  2. package/dist/cdn/svgrid-enterprise.svelte-external.js +14024 -6835
  3. package/dist/node/studio.js +7888 -2459
  4. package/package.json +9 -4
  5. package/src/SvGridMasterDetail.svelte +24 -3
  6. package/src/SvGridScheduler.svelte +4410 -0
  7. package/src/SvPivotDesigner.svelte +1990 -1045
  8. package/src/SvSchemaChart.svelte +10 -9
  9. package/src/ai.test.ts +522 -522
  10. package/src/ai.ts +202 -2
  11. package/src/index.ts +409 -384
  12. package/src/install.ts +10 -0
  13. package/src/pivot-chart.test.ts +86 -0
  14. package/src/pivot-chart.ts +112 -0
  15. package/src/scheduler.ts +37 -0
  16. package/src/scheduling.test.ts +194 -0
  17. package/src/scheduling.ts +293 -0
  18. package/src/sources/filters.ts +6 -0
  19. package/src/studio/HANDLERS-DESIGN.md +142 -0
  20. package/src/studio/cli.ts +7 -2
  21. package/src/studio/emit-project.test.ts +1447 -13
  22. package/src/studio/emit-project.ts +3995 -1273
  23. package/src/studio/emit-schema.ts +146 -29
  24. package/src/studio/index.ts +320 -195
  25. package/src/studio/project.test.ts +370 -0
  26. package/src/studio/project.ts +1146 -26
  27. package/src/studio/sample-data.ts +4 -1
  28. package/src/studio/samples/ats.ts +2 -2
  29. package/src/studio/samples/clinic.ts +4 -2
  30. package/src/studio/samples/crm.ts +16 -8
  31. package/src/studio/samples/events.ts +4 -2
  32. package/src/studio/samples/fleet.ts +4 -2
  33. package/src/studio/samples/gym.ts +4 -2
  34. package/src/studio/samples/hr.ts +3 -1
  35. package/src/studio/samples/live-data.ts +308 -308
  36. package/src/studio/samples/projects.ts +2 -2
  37. package/src/studio/samples/restaurant.ts +4 -2
  38. package/src/studio/samples/samples.test.ts +13 -5
  39. package/src/studio/samples/shared.ts +346 -305
  40. package/src/studio/samples/support.ts +3 -1
  41. package/src/studio/scaffold.test.ts +15 -1
  42. package/src/studio/scaffold.ts +16 -0
  43. package/src/studio/themes.ts +7 -0
  44. package/src/studio/ui-components.ts +472 -0
  45. package/src/sveltekit/transport.test.ts +26 -0
  46. package/src/sveltekit/transport.ts +50 -5
  47. package/dist/designer/assets/index-Dp44bTid.js +0 -939
  48. package/dist/designer/assets/index-RJp6x8tw.css +0 -1
  49. package/dist/designer/assets/jszip.min-CjMo-QGg.js +0 -2
  50. package/dist/designer/index.html +0 -13
@@ -139,6 +139,28 @@ export type KitHandlerOptions<TData extends RowData> = {
139
139
  * logged, so an audit-sink hiccup never fails the underlying write.
140
140
  */
141
141
  audit?: (entry: KitAuditEntry<TData>) => void | Promise<void>
142
+ /**
143
+ * Business-logic lifecycle hooks, run server-side around each mutation (the
144
+ * Studio generator compiles a screen's visual "triggers" into these). A
145
+ * `before*` hook may transform the payload (return a new object) or throw to
146
+ * reject the write (-> `422 { error }`), so a rule enforced here holds even if
147
+ * the client skips it. `after*` hooks run once the write succeeds (cascades,
148
+ * notifications) - a throw there fails the request (`500`).
149
+ */
150
+ hooks?: KitHooks<TData>
151
+ }
152
+
153
+ /** The SvelteKit event slice handed to every hook (read `locals` for the actor). */
154
+ export type KitEvent = { request: Request; locals?: Record<string, unknown> }
155
+
156
+ /** Server-side mutation lifecycle hooks (see `KitHandlerOptions.hooks`). */
157
+ export type KitHooks<TData extends RowData> = {
158
+ beforeCreate?: (ctx: { values: Partial<TData>; event: KitEvent }) => Partial<TData> | void | Promise<Partial<TData> | void>
159
+ afterCreate?: (ctx: { row: TData; event: KitEvent }) => void | Promise<void>
160
+ beforeUpdate?: (ctx: { id: string; patch: Partial<TData>; event: KitEvent }) => Partial<TData> | void | Promise<Partial<TData> | void>
161
+ afterUpdate?: (ctx: { id: string; row: TData; event: KitEvent }) => void | Promise<void>
162
+ beforeDelete?: (ctx: { id: string; event: KitEvent }) => void | Promise<void>
163
+ afterDelete?: (ctx: { id: string; event: KitEvent }) => void | Promise<void>
142
164
  }
143
165
 
144
166
  /** The change details handed to the `audit` hook after a successful mutation. */
@@ -166,7 +188,7 @@ const actionOf = <TData extends RowData>(msg: KitMessage<TData>): KitAction =>
166
188
  export function createKitHandlers<TData extends RowData>(
167
189
  options: KitHandlerOptions<TData>,
168
190
  ): KitHandlers {
169
- const { source, authorize, validate, audit } = options
191
+ const { source, authorize, validate, audit, hooks } = options
170
192
  const idField = options.schema.idField ?? options.schema.fields.find((f) => f.primaryKey)?.field ?? 'id'
171
193
  const rowId = (row: unknown): string | null => {
172
194
  const v = (row as Record<string, unknown> | null)?.[idField]
@@ -222,21 +244,44 @@ export function createKitHandlers<TData extends RowData>(
222
244
  }
223
245
  if (msg.kind === 'mutate') {
224
246
  const evt = { request, locals: event?.locals }
247
+ // A `before*` hook throwing is a business-rule rejection -> 422 (not a 500).
248
+ const runBefore = async <R>(fn: () => Promise<R>): Promise<R | Response> => {
249
+ try { return await fn() } catch (err) { return jsonResponse({ error: err instanceof Error ? err.message : 'rejected' }, 422) }
250
+ }
225
251
  if (msg.op === 'create') {
226
252
  if (!source.createRow) return jsonResponse({ error: 'create not supported' }, 405)
227
- const row = await source.createRow(msg.input)
228
- await fireAudit({ action: 'create', id: rowId(row), values: msg.input, result: row, event: evt })
253
+ let input = msg.input
254
+ if (hooks?.beforeCreate) {
255
+ const r = await runBefore(() => Promise.resolve(hooks.beforeCreate!({ values: input, event: evt })))
256
+ if (r instanceof Response) return r
257
+ if (r) input = r as Partial<TData>
258
+ }
259
+ const row = await source.createRow(input)
260
+ if (hooks?.afterCreate) await hooks.afterCreate({ row, event: evt })
261
+ await fireAudit({ action: 'create', id: rowId(row), values: input, result: row, event: evt })
229
262
  return jsonResponse(row)
230
263
  }
231
264
  if (msg.op === 'update') {
232
265
  if (!source.updateRow) return jsonResponse({ error: 'update not supported' }, 405)
233
- const row = await source.updateRow(msg.id, msg.patch)
234
- await fireAudit({ action: 'update', id: msg.id, values: msg.patch, result: row, event: evt })
266
+ let patch = msg.patch
267
+ if (hooks?.beforeUpdate) {
268
+ const r = await runBefore(() => Promise.resolve(hooks.beforeUpdate!({ id: msg.id, patch, event: evt })))
269
+ if (r instanceof Response) return r
270
+ if (r) patch = r as Partial<TData>
271
+ }
272
+ const row = await source.updateRow(msg.id, patch)
273
+ if (hooks?.afterUpdate) await hooks.afterUpdate({ id: msg.id, row, event: evt })
274
+ await fireAudit({ action: 'update', id: msg.id, values: patch, result: row, event: evt })
235
275
  return jsonResponse(row)
236
276
  }
237
277
  if (msg.op === 'delete') {
238
278
  if (!source.deleteRow) return jsonResponse({ error: 'delete not supported' }, 405)
279
+ if (hooks?.beforeDelete) {
280
+ const r = await runBefore(() => Promise.resolve(hooks.beforeDelete!({ id: msg.id, event: evt })))
281
+ if (r instanceof Response) return r
282
+ }
239
283
  await source.deleteRow(msg.id)
284
+ if (hooks?.afterDelete) await hooks.afterDelete({ id: msg.id, event: evt })
240
285
  await fireAudit({ action: 'delete', id: msg.id, event: evt })
241
286
  return jsonResponse({ ok: true })
242
287
  }