@pilotiq/pilotiq 0.21.0 → 0.23.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.
- package/.turbo/turbo-build.log +2 -2
- package/CHANGELOG.md +107 -0
- package/dist/Pilotiq.d.ts +72 -0
- package/dist/Pilotiq.d.ts.map +1 -1
- package/dist/Pilotiq.js +145 -0
- package/dist/Pilotiq.js.map +1 -1
- package/dist/PilotiqServiceProvider.d.ts +2 -0
- package/dist/PilotiqServiceProvider.d.ts.map +1 -1
- package/dist/PilotiqServiceProvider.js +60 -12
- package/dist/PilotiqServiceProvider.js.map +1 -1
- package/dist/actions/importFactory.d.ts +5 -0
- package/dist/actions/importFactory.d.ts.map +1 -1
- package/dist/actions/importFactory.js +20 -10
- package/dist/actions/importFactory.js.map +1 -1
- package/dist/orm/modelDefaults.d.ts +10 -1
- package/dist/orm/modelDefaults.d.ts.map +1 -1
- package/dist/orm/modelDefaults.js +7 -2
- package/dist/orm/modelDefaults.js.map +1 -1
- package/dist/pageData/forms.js +3 -3
- package/dist/pageData/forms.js.map +1 -1
- package/dist/pageData/misc.js +5 -5
- package/dist/pageData/misc.js.map +1 -1
- package/dist/pageData/navigation.d.ts.map +1 -1
- package/dist/pageData/navigation.js +11 -9
- package/dist/pageData/navigation.js.map +1 -1
- package/dist/pageData/relationPages.d.ts.map +1 -1
- package/dist/pageData/relationPages.js +7 -4
- package/dist/pageData/relationPages.js.map +1 -1
- package/dist/pageData/resourcePages.js +6 -6
- package/dist/pageData/resourcePages.js.map +1 -1
- package/dist/plugins/index.d.ts +3 -0
- package/dist/plugins/index.d.ts.map +1 -1
- package/dist/plugins/index.js +1 -0
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/themeEditor.d.ts +20 -1
- package/dist/plugins/themeEditor.d.ts.map +1 -1
- package/dist/plugins/themeEditor.js +3 -1
- package/dist/plugins/themeEditor.js.map +1 -1
- package/dist/react/CollabRoomContext.d.ts +12 -0
- package/dist/react/CollabRoomContext.d.ts.map +1 -1
- package/dist/react/CollabRoomContext.js.map +1 -1
- package/dist/react/FormStateContext.d.ts +10 -0
- package/dist/react/FormStateContext.d.ts.map +1 -1
- package/dist/react/FormStateContext.js +12 -0
- package/dist/react/FormStateContext.js.map +1 -1
- package/dist/react/PendingSuggestionApplierRegistry.d.ts +12 -0
- package/dist/react/PendingSuggestionApplierRegistry.d.ts.map +1 -1
- package/dist/react/PendingSuggestionApplierRegistry.js +21 -1
- package/dist/react/PendingSuggestionApplierRegistry.js.map +1 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.d.ts.map +1 -1
- package/dist/react/index.js +2 -1
- package/dist/react/index.js.map +1 -1
- package/dist/react/useCollabSeed.d.ts +23 -0
- package/dist/react/useCollabSeed.d.ts.map +1 -0
- package/dist/react/useCollabSeed.js +67 -0
- package/dist/react/useCollabSeed.js.map +1 -0
- package/dist/routes/globals.d.ts.map +1 -1
- package/dist/routes/globals.js +8 -22
- package/dist/routes/globals.js.map +1 -1
- package/dist/routes/helpers.d.ts +13 -0
- package/dist/routes/helpers.d.ts.map +1 -1
- package/dist/routes/helpers.js +25 -8
- package/dist/routes/helpers.js.map +1 -1
- package/dist/routes/resources.d.ts.map +1 -1
- package/dist/routes/resources.js +12 -34
- package/dist/routes/resources.js.map +1 -1
- package/dist/routes/theme.d.ts +4 -2
- package/dist/routes/theme.d.ts.map +1 -1
- package/dist/routes/theme.js +27 -26
- package/dist/routes/theme.js.map +1 -1
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +65 -37
- package/dist/routes.js.map +1 -1
- package/dist/theme/index.d.ts +2 -0
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +1 -0
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/storage.d.ts +86 -0
- package/dist/theme/storage.d.ts.map +1 -0
- package/dist/theme/storage.js +52 -0
- package/dist/theme/storage.js.map +1 -0
- package/package.json +1 -1
- package/src/Pilotiq.perf.test.ts +252 -0
- package/src/Pilotiq.test.ts +4 -0
- package/src/Pilotiq.ts +166 -0
- package/src/PilotiqServiceProvider.ts +63 -11
- package/src/actions/importFactory.ts +31 -10
- package/src/orm/modelDefaults.ts +15 -2
- package/src/pageData/forms.ts +3 -3
- package/src/pageData/misc.ts +5 -5
- package/src/pageData/navigation.ts +11 -9
- package/src/pageData/relationPages.ts +5 -3
- package/src/pageData/resourcePages.ts +6 -6
- package/src/plugins/index.ts +7 -0
- package/src/plugins/themeEditor.test.ts +36 -0
- package/src/plugins/themeEditor.ts +22 -1
- package/src/react/CollabRoomContext.ts +12 -0
- package/src/react/FormStateContext.tsx +13 -0
- package/src/react/PendingSuggestionApplierRegistry.test.ts +97 -0
- package/src/react/PendingSuggestionApplierRegistry.ts +19 -1
- package/src/react/index.ts +2 -0
- package/src/react/useCollabSeed.ts +73 -0
- package/src/routes/globals.ts +8 -16
- package/src/routes/guard.test.ts +325 -0
- package/src/routes/helpers.ts +30 -8
- package/src/routes/resources.ts +12 -22
- package/src/routes/theme.ts +26 -44
- package/src/routes.ts +65 -36
- package/src/theme/index.ts +6 -0
- package/src/theme/storage.test.ts +126 -0
- package/src/theme/storage.ts +106 -0
package/dist/routes.js
CHANGED
|
@@ -2,6 +2,7 @@ import { dispatchFormSubmit, findForms, selectForm } from './elements/dispatchFo
|
|
|
2
2
|
import { RESERVED_RELATIONSHIP_TOKENS } from './RelationManager.js';
|
|
3
3
|
import { Table } from './elements/Table.js';
|
|
4
4
|
import { Column } from './Column.js';
|
|
5
|
+
import { wantsJson } from './routes/helpers.js';
|
|
5
6
|
// `routes.ts` is split into a directory of focused modules under
|
|
6
7
|
// `./routes/`. This file is the orchestrator — boot-time validation
|
|
7
8
|
// loops + the per-Resource / per-Global / per-Page registration
|
|
@@ -170,43 +171,65 @@ export function registerPilotiqRoutes(router, pilotiq) {
|
|
|
170
171
|
editableEnabled.add(R.getSlug());
|
|
171
172
|
}
|
|
172
173
|
}
|
|
173
|
-
// ──
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
174
|
+
// ── `Pilotiq.guard()` — panel-wide 401 layer ──────────
|
|
175
|
+
// Documented as the unauthenticated-request gate, but until 2026-05-21
|
|
176
|
+
// only `_uploads` consulted it — every other route relied on
|
|
177
|
+
// `cfg.user` returning null + `R.canX(user, …)` defaulting to true,
|
|
178
|
+
// so an app that wired `guard(req => Auth.check())` but shipped any
|
|
179
|
+
// Resource without `canAccess` ended up with an unauthenticated,
|
|
180
|
+
// fully-readable admin panel. Wrap every core panel route in one
|
|
181
|
+
// group so the guard runs in front of every handler.
|
|
182
|
+
const guardMiddleware = async (req, res, next) => {
|
|
183
|
+
if (cfg.guard) {
|
|
184
|
+
const allowed = await cfg.guard(req);
|
|
185
|
+
if (!allowed) {
|
|
186
|
+
res.status(401);
|
|
187
|
+
if (wantsJson(req))
|
|
188
|
+
return res.json({ ok: false, error: 'Unauthorized' });
|
|
189
|
+
return res.send('Unauthorized');
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return next();
|
|
193
|
+
};
|
|
194
|
+
router.group({ middleware: [guardMiddleware] }, () => {
|
|
195
|
+
// ── Panel-level sibling routes ────────────────────────
|
|
196
|
+
// Dashboard, _uploads, _widget, _search, _notifications.
|
|
197
|
+
// Pulled out 2026-05-12 (Phase 2 of the routes.ts split).
|
|
198
|
+
registerPanelRoutes(router, pilotiq, base);
|
|
199
|
+
// ── Resource routes ───────────────────────────────────
|
|
200
|
+
// List / view / create / edit / delete + soft-delete / actions /
|
|
201
|
+
// widgets / deferred-table / reorder / per-row editable cells / the
|
|
202
|
+
// four form-state companion endpoints / record sub-pages. Each
|
|
203
|
+
// Resource also fans out into its registered relation managers
|
|
204
|
+
// (depth-1 + depth-2). Pulled out 2026-05-12 (Phase 5 of the
|
|
205
|
+
// routes.ts split).
|
|
206
|
+
for (const R of cfg.resources) {
|
|
207
|
+
registerResourceRoutes(router, pilotiq, R, base, {
|
|
208
|
+
reorderable: reorderEnabled.has(R.getSlug()),
|
|
209
|
+
editable: editableEnabled.has(R.getSlug()),
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
// ── Globals (singletons — 2-segment, no /:id) ────────
|
|
213
|
+
// Pulled out 2026-05-12 (Phase 3 of the routes.ts split).
|
|
214
|
+
for (const G of cfg.globals) {
|
|
215
|
+
registerGlobalRoutes(router, pilotiq, G, base);
|
|
216
|
+
}
|
|
217
|
+
// ── Custom pages (2-segment, slug route) ──────────────
|
|
218
|
+
// Pulled out 2026-05-12 (Phase 3 of the routes.ts split).
|
|
219
|
+
for (const PageClass of cfg.pages) {
|
|
220
|
+
// The dashboard page lives at `${base}` (panel routes handle it);
|
|
221
|
+
// skip it here so we don't register a duplicate `${pageUrl}` route
|
|
222
|
+
// or a broken `${base}/` (when `slug = ''`).
|
|
223
|
+
if (cfg.dashboardPage === PageClass)
|
|
224
|
+
continue;
|
|
225
|
+
registerCustomPageRoutes(router, pilotiq, PageClass, base);
|
|
226
|
+
}
|
|
227
|
+
// ── Theme editor ──────────────────────────────────────
|
|
228
|
+
// Pulled out 2026-05-12 (Phase 3 of the routes.ts split).
|
|
229
|
+
if (cfg.themeEditor) {
|
|
230
|
+
registerThemeRoutes(router, pilotiq, base);
|
|
231
|
+
}
|
|
232
|
+
});
|
|
210
233
|
// Plugin route hook — runs AFTER all core routes register so plugins
|
|
211
234
|
// can mount their own HTTP surface alongside the panel's. Order
|
|
212
235
|
// matches the registration order on `.use()` / `.plugins([…])`. Each
|
|
@@ -215,6 +238,11 @@ export function registerPilotiqRoutes(router, pilotiq) {
|
|
|
215
238
|
// `_notifications`) is the recommended shape. Failures inside a
|
|
216
239
|
// plugin's hook propagate — boot order is "register all core, then
|
|
217
240
|
// each plugin in order"; a throw on hook N stops hooks N+1..N+M.
|
|
241
|
+
//
|
|
242
|
+
// Plugin routes mount OUTSIDE the guard group — plugins own their
|
|
243
|
+
// own auth posture (e.g. public webhooks, custom auth handshakes).
|
|
244
|
+
// Plugin authors that want the panel guard should consult
|
|
245
|
+
// `cfg.guard` themselves at the top of their handlers.
|
|
218
246
|
for (const plugin of pilotiq.getPlugins()) {
|
|
219
247
|
plugin.registerRoutes?.(router, pilotiq);
|
|
220
248
|
}
|
package/dist/routes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"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"}
|
package/dist/theme/index.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export { iconMap, resolveIconName } from './icon-map.js';
|
|
|
10
10
|
export { colors, BASE_COLOR_NAMES, HUE_NAMES } from './colors.js';
|
|
11
11
|
export { parseSeedToScale } from './generate-scale.js';
|
|
12
12
|
export { migrateThemeOverrides } from './migrate.js';
|
|
13
|
+
export { prismaThemeStorage } from './storage.js';
|
|
14
|
+
export type { ThemeStorageAdapter, PanelGlobalDelegate, PrismaThemeStorageOptions, } from './storage.js';
|
|
13
15
|
export type { ColorName, ColorScale, ColorStep } from './colors.js';
|
|
14
16
|
export type { StylePreset, BaseColor, HueColor, ThemeColor, ChartColor, RadiusPreset, SpacingPreset, IconLibrary, ThemeFonts, ThemeConfig, ThemeMeta, PresetDefinition, } from './types.js';
|
|
15
17
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/theme/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AACnF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACxD,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/theme/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AACnF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACxD,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACjD,YAAY,EACV,mBAAmB,EACnB,mBAAmB,EACnB,yBAAyB,GAC1B,MAAM,cAAc,CAAA;AAErB,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACnE,YAAY,EACV,WAAW,EACX,SAAS,EACT,QAAQ,EACR,UAAU,EACV,UAAU,EACV,YAAY,EACZ,aAAa,EACb,WAAW,EACX,UAAU,EACV,WAAW,EACX,SAAS,EACT,gBAAgB,GACjB,MAAM,YAAY,CAAA"}
|
package/dist/theme/index.js
CHANGED
|
@@ -10,4 +10,5 @@ export { iconMap, resolveIconName } from './icon-map.js';
|
|
|
10
10
|
export { colors, BASE_COLOR_NAMES, HUE_NAMES } from './colors.js';
|
|
11
11
|
export { parseSeedToScale } from './generate-scale.js';
|
|
12
12
|
export { migrateThemeOverrides } from './migrate.js';
|
|
13
|
+
export { prismaThemeStorage } from './storage.js';
|
|
13
14
|
//# sourceMappingURL=index.js.map
|
package/dist/theme/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/theme/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AACnF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACxD,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/theme/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AACnF,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAA;AAC7C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AACzC,OAAO,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AACxD,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,aAAa,CAAA;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import type { ThemeConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Adapter that persists a panel's theme overrides — the JSON blob
|
|
4
|
+
* written when a user edits theme settings via the `themeEditor()`
|
|
5
|
+
* plugin and reloaded on next boot.
|
|
6
|
+
*
|
|
7
|
+
* The shipped implementation is `prismaThemeStorage`, which writes to
|
|
8
|
+
* the `panelGlobal` row created by `@rudderjs/orm-prisma`. Apps on a
|
|
9
|
+
* different ORM, key-value store, or filesystem can implement the
|
|
10
|
+
* three methods themselves.
|
|
11
|
+
*
|
|
12
|
+
* Contract:
|
|
13
|
+
*
|
|
14
|
+
* - `load()` returns `null` when no overrides have been persisted yet
|
|
15
|
+
* (fresh install). Throwing surfaces a configuration error to the
|
|
16
|
+
* caller — pilotiq does not swallow.
|
|
17
|
+
* - `save(overrides)` writes the blob verbatim. The next `load()` must
|
|
18
|
+
* return a deep-equal copy. Throwing surfaces to the route handler
|
|
19
|
+
* as a 500.
|
|
20
|
+
* - `clear()` deletes the row. Tolerating "not found" is the adapter's
|
|
21
|
+
* responsibility — `clear()` on an empty store is a no-op.
|
|
22
|
+
*/
|
|
23
|
+
export interface ThemeStorageAdapter {
|
|
24
|
+
load(): Promise<Partial<ThemeConfig> | null>;
|
|
25
|
+
save(overrides: Partial<ThemeConfig>): Promise<void>;
|
|
26
|
+
clear(): Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Minimal Prisma surface used by `prismaThemeStorage`. Narrow enough
|
|
30
|
+
* to keep the import surface decoupled from `PrismaClient`'s generated
|
|
31
|
+
* types — apps swap in any client whose `panelGlobal` delegate matches
|
|
32
|
+
* this shape.
|
|
33
|
+
*/
|
|
34
|
+
export interface PanelGlobalDelegate {
|
|
35
|
+
panelGlobal: {
|
|
36
|
+
findUnique(args: {
|
|
37
|
+
where: {
|
|
38
|
+
slug: string;
|
|
39
|
+
};
|
|
40
|
+
}): Promise<{
|
|
41
|
+
data: string | object | null;
|
|
42
|
+
} | null>;
|
|
43
|
+
upsert(args: {
|
|
44
|
+
where: {
|
|
45
|
+
slug: string;
|
|
46
|
+
};
|
|
47
|
+
update: {
|
|
48
|
+
data: string;
|
|
49
|
+
};
|
|
50
|
+
create: {
|
|
51
|
+
slug: string;
|
|
52
|
+
data: string;
|
|
53
|
+
};
|
|
54
|
+
}): Promise<unknown>;
|
|
55
|
+
delete(args: {
|
|
56
|
+
where: {
|
|
57
|
+
slug: string;
|
|
58
|
+
};
|
|
59
|
+
}): Promise<unknown>;
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export interface PrismaThemeStorageOptions {
|
|
63
|
+
/** Row key written to `panelGlobal.slug`. Pass per-panel so multiple
|
|
64
|
+
* panels in the same app don't clobber each other. Typically
|
|
65
|
+
* `${panel.name}__theme`. */
|
|
66
|
+
slug: string;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Default storage adapter — writes JSON to the `panelGlobal` row keyed
|
|
70
|
+
* by `opts.slug`. The Prisma delegate is dependency-injected so consumers
|
|
71
|
+
* pick how to resolve it (e.g. `app.make('prisma')`, a direct import, a
|
|
72
|
+
* test stub).
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* import { Pilotiq } from '@pilotiq/pilotiq'
|
|
77
|
+
* import { themeEditor, prismaThemeStorage } from '@pilotiq/pilotiq/plugins'
|
|
78
|
+
*
|
|
79
|
+
* const adminPanel = Pilotiq.make('Admin')
|
|
80
|
+
* .use(themeEditor({
|
|
81
|
+
* storage: prismaThemeStorage(prisma, { slug: 'admin__theme' }),
|
|
82
|
+
* }))
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
export declare function prismaThemeStorage(prisma: PanelGlobalDelegate, opts: PrismaThemeStorageOptions): ThemeStorageAdapter;
|
|
86
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/theme/storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,mBAAmB;IAClC,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI,CAAC,CAAA;IAC5C,IAAI,CAAC,SAAS,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACvB;AAED;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE;QACX,UAAU,CAAC,IAAI,EAAE;YAAE,KAAK,EAAE;gBAAE,IAAI,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,GAAG,OAAO,CAAC;YAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAA;SAAE,GAAG,IAAI,CAAC,CAAA;QAC/F,MAAM,CAAC,IAAI,EAAE;YACX,KAAK,EAAG;gBAAE,IAAI,EAAE,MAAM,CAAA;aAAE,CAAA;YACxB,MAAM,EAAE;gBAAE,IAAI,EAAE,MAAM,CAAA;aAAE,CAAA;YACxB,MAAM,EAAE;gBAAE,IAAI,EAAE,MAAM,CAAC;gBAAC,IAAI,EAAE,MAAM,CAAA;aAAE,CAAA;SACvC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;QACpB,MAAM,CAAC,IAAI,EAAE;YAAE,KAAK,EAAE;gBAAE,IAAI,EAAE,MAAM,CAAA;aAAE,CAAA;SAAE,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;KAC5D,CAAA;CACF;AAED,MAAM,WAAW,yBAAyB;IACxC;;kCAE8B;IAC9B,IAAI,EAAE,MAAM,CAAA;CACb;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,mBAAmB,EAC3B,IAAI,EAAI,yBAAyB,GAChC,mBAAmB,CAyBrB"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default storage adapter — writes JSON to the `panelGlobal` row keyed
|
|
3
|
+
* by `opts.slug`. The Prisma delegate is dependency-injected so consumers
|
|
4
|
+
* pick how to resolve it (e.g. `app.make('prisma')`, a direct import, a
|
|
5
|
+
* test stub).
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { Pilotiq } from '@pilotiq/pilotiq'
|
|
10
|
+
* import { themeEditor, prismaThemeStorage } from '@pilotiq/pilotiq/plugins'
|
|
11
|
+
*
|
|
12
|
+
* const adminPanel = Pilotiq.make('Admin')
|
|
13
|
+
* .use(themeEditor({
|
|
14
|
+
* storage: prismaThemeStorage(prisma, { slug: 'admin__theme' }),
|
|
15
|
+
* }))
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export function prismaThemeStorage(prisma, opts) {
|
|
19
|
+
const { slug } = opts;
|
|
20
|
+
return {
|
|
21
|
+
async load() {
|
|
22
|
+
const row = await prisma.panelGlobal.findUnique({ where: { slug } });
|
|
23
|
+
if (!row?.data)
|
|
24
|
+
return null;
|
|
25
|
+
const raw = typeof row.data === 'string' ? JSON.parse(row.data) : row.data;
|
|
26
|
+
return raw;
|
|
27
|
+
},
|
|
28
|
+
async save(overrides) {
|
|
29
|
+
const data = JSON.stringify(overrides);
|
|
30
|
+
await prisma.panelGlobal.upsert({
|
|
31
|
+
where: { slug },
|
|
32
|
+
update: { data },
|
|
33
|
+
create: { slug, data },
|
|
34
|
+
});
|
|
35
|
+
},
|
|
36
|
+
async clear() {
|
|
37
|
+
try {
|
|
38
|
+
await prisma.panelGlobal.delete({ where: { slug } });
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
if (!isRecordNotFound(e))
|
|
42
|
+
throw e;
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function isRecordNotFound(e) {
|
|
48
|
+
return typeof e === 'object'
|
|
49
|
+
&& e !== null
|
|
50
|
+
&& e.code === 'P2025';
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/theme/storage.ts"],"names":[],"mappings":"AAsDA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB,CAChC,MAA2B,EAC3B,IAAiC;IAEjC,MAAM,EAAE,IAAI,EAAE,GAAG,IAAI,CAAA;IACrB,OAAO;QACL,KAAK,CAAC,IAAI;YACR,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,WAAW,CAAC,UAAU,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;YACpE,IAAI,CAAC,GAAG,EAAE,IAAI;gBAAE,OAAO,IAAI,CAAA;YAC3B,MAAM,GAAG,GAAG,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAA;YAC1E,OAAO,GAA2B,CAAA;QACpC,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,SAAS;YAClB,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAA;YACtC,MAAM,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC;gBAC9B,KAAK,EAAG,EAAE,IAAI,EAAE;gBAChB,MAAM,EAAE,EAAE,IAAI,EAAE;gBAChB,MAAM,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE;aACvB,CAAC,CAAA;QACJ,CAAC;QACD,KAAK,CAAC,KAAK;YACT,IAAI,CAAC;gBACH,MAAM,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,EAAE,CAAC,CAAA;YACtD,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC;oBAAE,MAAM,CAAC,CAAA;YACnC,CAAC;QACH,CAAC;KACF,CAAA;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,CAAU;IAClC,OAAO,OAAO,CAAC,KAAK,QAAQ;WACvB,CAAC,KAAK,IAAI;WACT,CAAuB,CAAC,IAAI,KAAK,OAAO,CAAA;AAChD,CAAC"}
|
package/package.json
CHANGED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase 5 perf sweep — covers the four hot-path changes that landed
|
|
3
|
+
* 2026-05-22:
|
|
4
|
+
*
|
|
5
|
+
* - 5b Per-user navigation-badge TTL cache (`Pilotiq.navigationBadgeTtl`)
|
|
6
|
+
* - 5c Map-based slug lookup (`Pilotiq.findResource/findGlobal/findPage`)
|
|
7
|
+
* - 5a Chunked import (`importFactory.runImport` honors `concurrency`)
|
|
8
|
+
*
|
|
9
|
+
* 5d (`policyGate`) is exercised indirectly by the existing routes /
|
|
10
|
+
* authorization tests — its contract is identical to the prior
|
|
11
|
+
* serial pair, just parallelized; no behavior change to assert here.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it } from 'node:test'
|
|
14
|
+
import assert from 'node:assert/strict'
|
|
15
|
+
|
|
16
|
+
import { Pilotiq } from './Pilotiq.js'
|
|
17
|
+
import { Resource } from './Resource.js'
|
|
18
|
+
import { Global } from './Global.js'
|
|
19
|
+
import { Page } from './Page.js'
|
|
20
|
+
import { runImport } from './actions/importFactory.js'
|
|
21
|
+
|
|
22
|
+
// ─── Fixtures ─────────────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
class Articles extends Resource {
|
|
25
|
+
static override slug = 'articles'
|
|
26
|
+
static override label = 'Articles'
|
|
27
|
+
}
|
|
28
|
+
class Comments extends Resource {
|
|
29
|
+
static override slug = 'comments'
|
|
30
|
+
static override label = 'Comments'
|
|
31
|
+
}
|
|
32
|
+
class Settings extends Global {
|
|
33
|
+
static override slug = 'settings'
|
|
34
|
+
static override label = 'Settings'
|
|
35
|
+
}
|
|
36
|
+
class Branding extends Global {
|
|
37
|
+
static override slug = 'branding'
|
|
38
|
+
static override label = 'Branding'
|
|
39
|
+
}
|
|
40
|
+
class Reports extends Page {
|
|
41
|
+
static override slug = 'reports'
|
|
42
|
+
static override label = 'Reports'
|
|
43
|
+
}
|
|
44
|
+
class Health extends Page {
|
|
45
|
+
static override slug = 'health'
|
|
46
|
+
static override label = 'Health'
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ─── 5c — Map-based slug lookup ───────────────────────────────
|
|
50
|
+
|
|
51
|
+
describe('Pilotiq.find{Resource,Global,Page}() — Plan 5c', () => {
|
|
52
|
+
it('returns the matching class by slug', () => {
|
|
53
|
+
const p = Pilotiq.make('admin')
|
|
54
|
+
.resources([Articles, Comments])
|
|
55
|
+
.globals([Settings, Branding])
|
|
56
|
+
.pages([Reports, Health])
|
|
57
|
+
assert.equal(p.findResource('articles'), Articles)
|
|
58
|
+
assert.equal(p.findResource('comments'), Comments)
|
|
59
|
+
assert.equal(p.findGlobal('settings'), Settings)
|
|
60
|
+
assert.equal(p.findGlobal('branding'), Branding)
|
|
61
|
+
assert.equal(p.findPage('reports'), Reports)
|
|
62
|
+
assert.equal(p.findPage('health'), Health)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
it('returns undefined for unknown slugs', () => {
|
|
66
|
+
const p = Pilotiq.make('admin').resources([Articles])
|
|
67
|
+
assert.equal(p.findResource('nope'), undefined)
|
|
68
|
+
assert.equal(p.findGlobal('nope'), undefined)
|
|
69
|
+
assert.equal(p.findPage('nope'), undefined)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('invalidates the cache when .resources() is reassigned', () => {
|
|
73
|
+
const p = Pilotiq.make('admin').resources([Articles])
|
|
74
|
+
assert.equal(p.findResource('articles'), Articles)
|
|
75
|
+
assert.equal(p.findResource('comments'), undefined)
|
|
76
|
+
p.resources([Articles, Comments])
|
|
77
|
+
assert.equal(p.findResource('comments'), Comments)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
it('invalidates the page cache when .pages() is reassigned', () => {
|
|
81
|
+
const p = Pilotiq.make('admin').pages([Reports])
|
|
82
|
+
assert.equal(p.findPage('reports'), Reports)
|
|
83
|
+
assert.equal(p.findPage('health'), undefined)
|
|
84
|
+
p.pages([Reports, Health])
|
|
85
|
+
assert.equal(p.findPage('health'), Health)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('invalidates the page cache when .dashboard()/.profile() auto-append', () => {
|
|
89
|
+
class Dash extends Page {
|
|
90
|
+
static override slug = 'dash'
|
|
91
|
+
static override label = 'Dashboard'
|
|
92
|
+
}
|
|
93
|
+
const p = Pilotiq.make('admin')
|
|
94
|
+
assert.equal(p.findPage('dash'), undefined)
|
|
95
|
+
p.dashboard(Dash)
|
|
96
|
+
assert.equal(p.findPage('dash'), Dash)
|
|
97
|
+
})
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
// ─── 5b — Navigation badge TTL cache ──────────────────────────
|
|
101
|
+
|
|
102
|
+
describe('Pilotiq.navigationBadgeTtl() + resolveNavigationBadge() — Plan 5b', () => {
|
|
103
|
+
it('default TTL is 30s', () => {
|
|
104
|
+
const p = Pilotiq.make('admin')
|
|
105
|
+
assert.equal(p.getNavigationBadgeTtl(), 30_000)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('navigationBadgeTtl(ms) overrides; clamps negatives to 0', () => {
|
|
109
|
+
const p = Pilotiq.make('admin').navigationBadgeTtl(5_000)
|
|
110
|
+
assert.equal(p.getNavigationBadgeTtl(), 5_000)
|
|
111
|
+
p.navigationBadgeTtl(-1)
|
|
112
|
+
assert.equal(p.getNavigationBadgeTtl(), 0)
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('navigationBadgeTtl(null) restores the default', () => {
|
|
116
|
+
const p = Pilotiq.make('admin').navigationBadgeTtl(1_000)
|
|
117
|
+
p.navigationBadgeTtl(null)
|
|
118
|
+
assert.equal(p.getNavigationBadgeTtl(), 30_000)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('resolveNavigationBadge caches within TTL, busts on user change', async () => {
|
|
122
|
+
const p = Pilotiq.make('admin')
|
|
123
|
+
let calls = 0
|
|
124
|
+
const resolver = async () => { calls++; return String(calls) }
|
|
125
|
+
|
|
126
|
+
// First call: miss → resolver fires → returns '1'.
|
|
127
|
+
assert.equal(await p.resolveNavigationBadge('Articles', { id: 1 }, resolver), '1')
|
|
128
|
+
assert.equal(calls, 1)
|
|
129
|
+
// Same user + owner: hit → no new call → still '1'.
|
|
130
|
+
assert.equal(await p.resolveNavigationBadge('Articles', { id: 1 }, resolver), '1')
|
|
131
|
+
assert.equal(calls, 1)
|
|
132
|
+
// Different user: miss → resolver fires again.
|
|
133
|
+
assert.equal(await p.resolveNavigationBadge('Articles', { id: 2 }, resolver), '2')
|
|
134
|
+
assert.equal(calls, 2)
|
|
135
|
+
// Different owner, same user: separate cache slot.
|
|
136
|
+
assert.equal(await p.resolveNavigationBadge('Comments', { id: 1 }, resolver), '3')
|
|
137
|
+
assert.equal(calls, 3)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('TTL of 0 disables caching entirely', async () => {
|
|
141
|
+
const p = Pilotiq.make('admin').navigationBadgeTtl(0)
|
|
142
|
+
let calls = 0
|
|
143
|
+
const resolver = async () => { calls++; return 'x' }
|
|
144
|
+
await p.resolveNavigationBadge('Articles', { id: 1 }, resolver)
|
|
145
|
+
await p.resolveNavigationBadge('Articles', { id: 1 }, resolver)
|
|
146
|
+
assert.equal(calls, 2)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('caches undefined results (no need to keep re-resolving "no badge")', async () => {
|
|
150
|
+
const p = Pilotiq.make('admin')
|
|
151
|
+
let calls = 0
|
|
152
|
+
const resolver = async () => { calls++; return undefined }
|
|
153
|
+
assert.equal(await p.resolveNavigationBadge('Articles', null, resolver), undefined)
|
|
154
|
+
assert.equal(await p.resolveNavigationBadge('Articles', null, resolver), undefined)
|
|
155
|
+
assert.equal(calls, 1)
|
|
156
|
+
})
|
|
157
|
+
|
|
158
|
+
it('navigationBadgeTtl(ms) clears the cache', async () => {
|
|
159
|
+
const p = Pilotiq.make('admin')
|
|
160
|
+
let calls = 0
|
|
161
|
+
const resolver = async () => { calls++; return 'x' }
|
|
162
|
+
await p.resolveNavigationBadge('A', null, resolver)
|
|
163
|
+
assert.equal(calls, 1)
|
|
164
|
+
p.navigationBadgeTtl(60_000)
|
|
165
|
+
await p.resolveNavigationBadge('A', null, resolver)
|
|
166
|
+
assert.equal(calls, 2)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('anonymous users share one cache slot', async () => {
|
|
170
|
+
const p = Pilotiq.make('admin')
|
|
171
|
+
let calls = 0
|
|
172
|
+
const resolver = async () => { calls++; return 'x' }
|
|
173
|
+
await p.resolveNavigationBadge('A', null, resolver)
|
|
174
|
+
await p.resolveNavigationBadge('A', undefined, resolver)
|
|
175
|
+
assert.equal(calls, 1)
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
it('falls back to JSON.stringify when user has no .id', async () => {
|
|
179
|
+
const p = Pilotiq.make('admin')
|
|
180
|
+
let calls = 0
|
|
181
|
+
const resolver = async () => { calls++; return 'x' }
|
|
182
|
+
await p.resolveNavigationBadge('A', { role: 'editor' }, resolver)
|
|
183
|
+
await p.resolveNavigationBadge('A', { role: 'editor' }, resolver)
|
|
184
|
+
assert.equal(calls, 1) // same JSON shape → cache hit
|
|
185
|
+
await p.resolveNavigationBadge('A', { role: 'admin' }, resolver)
|
|
186
|
+
assert.equal(calls, 2) // different JSON → miss
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
// ─── 5a — Chunked importFactory.runImport ─────────────────────
|
|
191
|
+
|
|
192
|
+
describe('importFactory.runImport — Plan 5a chunking', () => {
|
|
193
|
+
it('runs rows in chunks of `concurrency` and aggregates counts', async () => {
|
|
194
|
+
const created: string[] = []
|
|
195
|
+
let maxInFlight = 0
|
|
196
|
+
let inFlight = 0
|
|
197
|
+
const M = {
|
|
198
|
+
async create(row: { id: string }) {
|
|
199
|
+
inFlight++; if (inFlight > maxInFlight) maxInFlight = inFlight
|
|
200
|
+
await new Promise(r => setTimeout(r, 5))
|
|
201
|
+
inFlight--
|
|
202
|
+
created.push(row.id)
|
|
203
|
+
},
|
|
204
|
+
// unused for create-mode tests but the type wants them present
|
|
205
|
+
query() { return { where() { return { paginate: async () => ({ data: [] }) } } } },
|
|
206
|
+
async update() {},
|
|
207
|
+
}
|
|
208
|
+
const rows = Array.from({ length: 25 }, (_, i) => ({ id: `r${i}` }))
|
|
209
|
+
const summary = await runImport(rows, M, 'create', { concurrency: 5 }, { request: undefined })
|
|
210
|
+
assert.equal(summary.created, 25)
|
|
211
|
+
assert.equal(summary.errors.length, 0)
|
|
212
|
+
// With concurrency=5 we should see at least 4 in-flight at peak.
|
|
213
|
+
assert.ok(maxInFlight >= 4, `expected >=4 concurrent, saw ${maxInFlight}`)
|
|
214
|
+
// Never exceed the cap.
|
|
215
|
+
assert.ok(maxInFlight <= 5, `expected <=5 concurrent, saw ${maxInFlight}`)
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it('preserves original-row indices in error messages despite chunking', async () => {
|
|
219
|
+
const M = {
|
|
220
|
+
async create(row: { id: string }) {
|
|
221
|
+
if (row.id === 'r2') throw new Error('boom')
|
|
222
|
+
},
|
|
223
|
+
query() { return { where() { return { paginate: async () => ({ data: [] }) } } } },
|
|
224
|
+
async update() {},
|
|
225
|
+
}
|
|
226
|
+
const rows = [{ id: 'r0' }, { id: 'r1' }, { id: 'r2' }, { id: 'r3' }]
|
|
227
|
+
const summary = await runImport(rows, M, 'create', { concurrency: 4 }, { request: undefined })
|
|
228
|
+
assert.equal(summary.created, 3)
|
|
229
|
+
assert.equal(summary.skipped, 1)
|
|
230
|
+
assert.equal(summary.errors.length, 1)
|
|
231
|
+
assert.equal(summary.errors[0]?.row, 3) // 1-based, original index 2 → row 3
|
|
232
|
+
assert.match(summary.errors[0]?.message ?? '', /boom/)
|
|
233
|
+
})
|
|
234
|
+
|
|
235
|
+
it('defaults to concurrency 10 when unset', async () => {
|
|
236
|
+
let maxInFlight = 0
|
|
237
|
+
let inFlight = 0
|
|
238
|
+
const M = {
|
|
239
|
+
async create() {
|
|
240
|
+
inFlight++; if (inFlight > maxInFlight) maxInFlight = inFlight
|
|
241
|
+
await new Promise(r => setTimeout(r, 3))
|
|
242
|
+
inFlight--
|
|
243
|
+
},
|
|
244
|
+
query() { return { where() { return { paginate: async () => ({ data: [] }) } } } },
|
|
245
|
+
async update() {},
|
|
246
|
+
}
|
|
247
|
+
const rows = Array.from({ length: 30 }, () => ({}))
|
|
248
|
+
await runImport(rows, M, 'create', {}, { request: undefined })
|
|
249
|
+
assert.ok(maxInFlight <= 10, `expected <=10 concurrent, saw ${maxInFlight}`)
|
|
250
|
+
assert.ok(maxInFlight >= 5, `expected >=5 concurrent under default, saw ${maxInFlight}`)
|
|
251
|
+
})
|
|
252
|
+
})
|
package/src/Pilotiq.test.ts
CHANGED
|
@@ -62,6 +62,10 @@ function makeStubRouter(): Router & { _calls: Array<{ method: string; path: stri
|
|
|
62
62
|
put: (path: string) => noop(path),
|
|
63
63
|
delete: (path: string) => noop(path),
|
|
64
64
|
patch: (path: string) => noop(path),
|
|
65
|
+
// `router.group(opts, fn)` runs `fn()` synchronously inside its
|
|
66
|
+
// scope. Stub mirrors that — `Pilotiq.guard()` middleware doesn't
|
|
67
|
+
// touch the stub, only `fn()` matters.
|
|
68
|
+
group: (_opts: unknown, fn: () => void) => { fn() },
|
|
65
69
|
_calls: calls,
|
|
66
70
|
}
|
|
67
71
|
return stub
|