stagent 0.6.2 → 0.7.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/README.md +21 -2
- package/dist/cli.js +272 -1
- package/docs/.coverage-gaps.json +66 -16
- package/docs/.last-generated +1 -1
- package/docs/features/dashboard-kanban.md +13 -7
- package/docs/features/settings.md +15 -3
- package/docs/features/tables.md +122 -0
- package/docs/index.md +3 -2
- package/docs/journeys/developer.md +26 -16
- package/docs/journeys/personal-use.md +23 -9
- package/docs/journeys/power-user.md +40 -14
- package/docs/journeys/work-use.md +43 -15
- package/docs/manifest.json +27 -17
- package/package.json +3 -2
- package/src/app/api/chat/entities/search/route.ts +12 -3
- package/src/app/api/documents/[id]/route.ts +5 -1
- package/src/app/api/documents/[id]/versions/route.ts +53 -0
- package/src/app/api/documents/route.ts +5 -1
- package/src/app/api/projects/[id]/documents/route.ts +124 -0
- package/src/app/api/projects/[id]/route.ts +72 -3
- package/src/app/api/projects/__tests__/delete-project.test.ts +13 -0
- package/src/app/api/schedules/route.ts +19 -1
- package/src/app/api/snapshots/[id]/restore/route.ts +62 -0
- package/src/app/api/snapshots/[id]/route.ts +44 -0
- package/src/app/api/snapshots/route.ts +54 -0
- package/src/app/api/snapshots/settings/route.ts +67 -0
- package/src/app/api/tables/[id]/charts/[chartId]/route.ts +89 -0
- package/src/app/api/tables/[id]/charts/route.ts +72 -0
- package/src/app/api/tables/[id]/columns/route.ts +70 -0
- package/src/app/api/tables/[id]/export/route.ts +94 -0
- package/src/app/api/tables/[id]/history/route.ts +15 -0
- package/src/app/api/tables/[id]/import/route.ts +111 -0
- package/src/app/api/tables/[id]/route.ts +86 -0
- package/src/app/api/tables/[id]/rows/[rowId]/history/route.ts +32 -0
- package/src/app/api/tables/[id]/rows/[rowId]/route.ts +51 -0
- package/src/app/api/tables/[id]/rows/route.ts +101 -0
- package/src/app/api/tables/[id]/triggers/[triggerId]/route.ts +65 -0
- package/src/app/api/tables/[id]/triggers/route.ts +122 -0
- package/src/app/api/tables/route.ts +65 -0
- package/src/app/api/tables/templates/route.ts +92 -0
- package/src/app/api/tasks/[id]/route.ts +37 -2
- package/src/app/api/tasks/[id]/siblings/route.ts +48 -0
- package/src/app/api/tasks/route.ts +8 -9
- package/src/app/api/workflows/[id]/documents/route.ts +209 -0
- package/src/app/api/workflows/[id]/execute/route.ts +6 -2
- package/src/app/api/workflows/[id]/route.ts +16 -3
- package/src/app/api/workflows/[id]/status/route.ts +18 -2
- package/src/app/api/workflows/route.ts +13 -2
- package/src/app/documents/page.tsx +5 -1
- package/src/app/layout.tsx +0 -1
- package/src/app/manifest.ts +3 -3
- package/src/app/projects/[id]/page.tsx +62 -2
- package/src/app/settings/page.tsx +2 -0
- package/src/app/tables/[id]/page.tsx +67 -0
- package/src/app/tables/page.tsx +21 -0
- package/src/app/tables/templates/page.tsx +19 -0
- package/src/components/chat/chat-table-result.tsx +139 -0
- package/src/components/documents/document-browser.tsx +1 -1
- package/src/components/documents/document-chip-bar.tsx +17 -1
- package/src/components/documents/document-detail-view.tsx +51 -0
- package/src/components/documents/document-grid.tsx +5 -0
- package/src/components/documents/document-table.tsx +4 -0
- package/src/components/documents/types.ts +3 -0
- package/src/components/projects/project-form-sheet.tsx +109 -2
- package/src/components/schedules/schedule-form.tsx +91 -1
- package/src/components/settings/data-management-section.tsx +17 -12
- package/src/components/settings/database-snapshots-section.tsx +469 -0
- package/src/components/shared/app-sidebar.tsx +2 -0
- package/src/components/shared/document-picker-sheet.tsx +486 -0
- package/src/components/tables/table-browser.tsx +234 -0
- package/src/components/tables/table-cell-editor.tsx +226 -0
- package/src/components/tables/table-chart-builder.tsx +288 -0
- package/src/components/tables/table-chart-view.tsx +146 -0
- package/src/components/tables/table-column-header.tsx +103 -0
- package/src/components/tables/table-column-sheet.tsx +331 -0
- package/src/components/tables/table-create-sheet.tsx +240 -0
- package/src/components/tables/table-detail-sheet.tsx +144 -0
- package/src/components/tables/table-detail-tabs.tsx +278 -0
- package/src/components/tables/table-grid.tsx +61 -0
- package/src/components/tables/table-history-tab.tsx +148 -0
- package/src/components/tables/table-import-wizard.tsx +542 -0
- package/src/components/tables/table-list-table.tsx +95 -0
- package/src/components/tables/table-relation-combobox.tsx +217 -0
- package/src/components/tables/table-spreadsheet.tsx +499 -0
- package/src/components/tables/table-template-gallery.tsx +162 -0
- package/src/components/tables/table-template-preview.tsx +219 -0
- package/src/components/tables/table-toolbar.tsx +79 -0
- package/src/components/tables/table-triggers-tab.tsx +446 -0
- package/src/components/tables/types.ts +6 -0
- package/src/components/tables/use-spreadsheet-keys.ts +171 -0
- package/src/components/tables/utils.ts +29 -0
- package/src/components/tasks/task-card.tsx +8 -1
- package/src/components/tasks/task-create-panel.tsx +111 -14
- package/src/components/tasks/task-detail-view.tsx +47 -0
- package/src/components/tasks/task-edit-dialog.tsx +103 -2
- package/src/components/workflows/workflow-form-view.tsx +207 -7
- package/src/components/workflows/workflow-kanban-card.tsx +8 -1
- package/src/components/workflows/workflow-list.tsx +90 -45
- package/src/components/workflows/workflow-status-view.tsx +168 -23
- package/src/instrumentation.ts +3 -0
- package/src/lib/__tests__/npx-process-cwd.test.ts +17 -2
- package/src/lib/agents/__tests__/claude-agent.test.ts +5 -1
- package/src/lib/agents/claude-agent.ts +3 -1
- package/src/lib/agents/profiles/registry.ts +6 -3
- package/src/lib/agents/runtime/anthropic-direct.ts +29 -0
- package/src/lib/agents/runtime/openai-direct.ts +29 -0
- package/src/lib/book/__tests__/chapter-slugs.test.ts +80 -0
- package/src/lib/book/chapter-generator.ts +4 -19
- package/src/lib/book/chapter-mapping.ts +17 -0
- package/src/lib/book/content.ts +5 -16
- package/src/lib/book/update-detector.ts +3 -16
- package/src/lib/chat/engine.ts +1 -0
- package/src/lib/chat/stagent-tools.ts +2 -0
- package/src/lib/chat/system-prompt.ts +9 -1
- package/src/lib/chat/tool-catalog.ts +35 -0
- package/src/lib/chat/tools/settings-tools.ts +109 -0
- package/src/lib/chat/tools/table-tools.ts +955 -0
- package/src/lib/chat/tools/workflow-tools.ts +145 -2
- package/src/lib/constants/table-status.ts +68 -0
- package/src/lib/data/__tests__/clear.test.ts +1 -1
- package/src/lib/data/clear.ts +57 -0
- package/src/lib/data/seed-data/__tests__/profiles.test.ts +28 -23
- package/src/lib/data/seed-data/conversations.ts +350 -42
- package/src/lib/data/seed-data/documents.ts +564 -591
- package/src/lib/data/seed-data/learned-context.ts +101 -22
- package/src/lib/data/seed-data/notifications.ts +344 -70
- package/src/lib/data/seed-data/profile-test-results.ts +92 -11
- package/src/lib/data/seed-data/profiles.ts +144 -46
- package/src/lib/data/seed-data/projects.ts +50 -18
- package/src/lib/data/seed-data/repo-imports.ts +28 -13
- package/src/lib/data/seed-data/schedules.ts +208 -41
- package/src/lib/data/seed-data/table-templates.ts +234 -0
- package/src/lib/data/seed-data/tasks.ts +614 -116
- package/src/lib/data/seed-data/usage-ledger.ts +182 -103
- package/src/lib/data/seed-data/user-tables.ts +203 -0
- package/src/lib/data/seed-data/views.ts +52 -7
- package/src/lib/data/seed-data/workflows.ts +231 -84
- package/src/lib/data/seed.ts +55 -14
- package/src/lib/data/tables.ts +417 -0
- package/src/lib/db/bootstrap.ts +275 -0
- package/src/lib/db/index.ts +9 -0
- package/src/lib/db/migrations/0016_add_workflow_document_inputs.sql +13 -0
- package/src/lib/db/migrations/0017_add_document_picker_tables.sql +25 -0
- package/src/lib/db/migrations/0018_add_workflow_run_number.sql +2 -0
- package/src/lib/db/migrations/0019_add_tables_feature.sql +160 -0
- package/src/lib/db/migrations/0020_add_table_triggers.sql +19 -0
- package/src/lib/db/migrations/0021_add_row_history.sql +15 -0
- package/src/lib/db/schema.ts +445 -0
- package/src/lib/docs/reader.ts +2 -3
- package/src/lib/documents/context-builder.ts +75 -2
- package/src/lib/documents/document-resolver.ts +119 -0
- package/src/lib/documents/processors/spreadsheet.ts +2 -1
- package/src/lib/schedules/scheduler.ts +31 -1
- package/src/lib/snapshots/auto-backup.ts +132 -0
- package/src/lib/snapshots/retention.ts +64 -0
- package/src/lib/snapshots/snapshot-manager.ts +429 -0
- package/src/lib/tables/computed.ts +61 -0
- package/src/lib/tables/context-builder.ts +139 -0
- package/src/lib/tables/formula-engine.ts +415 -0
- package/src/lib/tables/history.ts +115 -0
- package/src/lib/tables/import.ts +343 -0
- package/src/lib/tables/query-builder.ts +152 -0
- package/src/lib/tables/trigger-evaluator.ts +146 -0
- package/src/lib/tables/types.ts +141 -0
- package/src/lib/tables/validation.ts +119 -0
- package/src/lib/utils/app-root.ts +20 -0
- package/src/lib/utils/stagent-paths.ts +20 -0
- package/src/lib/validators/__tests__/task.test.ts +43 -10
- package/src/lib/validators/task.ts +7 -1
- package/src/lib/workflows/blueprints/registry.ts +3 -3
- package/src/lib/workflows/engine.ts +24 -8
- package/src/lib/workflows/types.ts +14 -0
- package/tsconfig.json +3 -1
- package/public/icon.svg +0 -13
- package/src/components/tasks/file-upload.tsx +0 -120
- /package/docs/features/{playbook.md → user-guide.md} +0 -0
package/src/lib/db/schema.ts
CHANGED
|
@@ -37,6 +37,7 @@ export const tasks = sqliteTable(
|
|
|
37
37
|
sourceType: text("source_type", {
|
|
38
38
|
enum: ["manual", "scheduled", "heartbeat", "workflow"],
|
|
39
39
|
}),
|
|
40
|
+
workflowRunNumber: integer("workflow_run_number"),
|
|
40
41
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
41
42
|
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
|
|
42
43
|
},
|
|
@@ -59,6 +60,7 @@ export const workflows = sqliteTable("workflows", {
|
|
|
59
60
|
})
|
|
60
61
|
.default("draft")
|
|
61
62
|
.notNull(),
|
|
63
|
+
runNumber: integer("run_number").default(0).notNull(),
|
|
62
64
|
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
63
65
|
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
|
|
64
66
|
});
|
|
@@ -678,6 +680,407 @@ export const agentMessages = sqliteTable(
|
|
|
678
680
|
]
|
|
679
681
|
);
|
|
680
682
|
|
|
683
|
+
// ── Workflow Document Pool ───────────────────────────────────────────
|
|
684
|
+
|
|
685
|
+
export const workflowDocumentInputs = sqliteTable(
|
|
686
|
+
"workflow_document_inputs",
|
|
687
|
+
{
|
|
688
|
+
id: text("id").primaryKey(),
|
|
689
|
+
workflowId: text("workflow_id")
|
|
690
|
+
.references(() => workflows.id)
|
|
691
|
+
.notNull(),
|
|
692
|
+
documentId: text("document_id")
|
|
693
|
+
.references(() => documents.id)
|
|
694
|
+
.notNull(),
|
|
695
|
+
/** null = document available to all steps; set = scoped to specific step */
|
|
696
|
+
stepId: text("step_id"),
|
|
697
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
698
|
+
},
|
|
699
|
+
(table) => [
|
|
700
|
+
index("idx_wdi_workflow").on(table.workflowId),
|
|
701
|
+
index("idx_wdi_document").on(table.documentId),
|
|
702
|
+
uniqueIndex("idx_wdi_workflow_doc_step").on(
|
|
703
|
+
table.workflowId,
|
|
704
|
+
table.documentId,
|
|
705
|
+
table.stepId
|
|
706
|
+
),
|
|
707
|
+
]
|
|
708
|
+
);
|
|
709
|
+
|
|
710
|
+
export type WorkflowDocumentInputRow = InferSelectModel<typeof workflowDocumentInputs>;
|
|
711
|
+
|
|
712
|
+
export const scheduleDocumentInputs = sqliteTable(
|
|
713
|
+
"schedule_document_inputs",
|
|
714
|
+
{
|
|
715
|
+
id: text("id").primaryKey(),
|
|
716
|
+
scheduleId: text("schedule_id")
|
|
717
|
+
.references(() => schedules.id)
|
|
718
|
+
.notNull(),
|
|
719
|
+
documentId: text("document_id")
|
|
720
|
+
.references(() => documents.id)
|
|
721
|
+
.notNull(),
|
|
722
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
723
|
+
},
|
|
724
|
+
(table) => [
|
|
725
|
+
index("idx_sdi_schedule").on(table.scheduleId),
|
|
726
|
+
uniqueIndex("idx_sdi_schedule_doc").on(
|
|
727
|
+
table.scheduleId,
|
|
728
|
+
table.documentId
|
|
729
|
+
),
|
|
730
|
+
]
|
|
731
|
+
);
|
|
732
|
+
|
|
733
|
+
export type ScheduleDocumentInputRow = InferSelectModel<typeof scheduleDocumentInputs>;
|
|
734
|
+
|
|
735
|
+
export const projectDocumentDefaults = sqliteTable(
|
|
736
|
+
"project_document_defaults",
|
|
737
|
+
{
|
|
738
|
+
id: text("id").primaryKey(),
|
|
739
|
+
projectId: text("project_id")
|
|
740
|
+
.references(() => projects.id)
|
|
741
|
+
.notNull(),
|
|
742
|
+
documentId: text("document_id")
|
|
743
|
+
.references(() => documents.id)
|
|
744
|
+
.notNull(),
|
|
745
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
746
|
+
},
|
|
747
|
+
(table) => [
|
|
748
|
+
index("idx_pdd_project").on(table.projectId),
|
|
749
|
+
uniqueIndex("idx_pdd_project_doc").on(
|
|
750
|
+
table.projectId,
|
|
751
|
+
table.documentId
|
|
752
|
+
),
|
|
753
|
+
]
|
|
754
|
+
);
|
|
755
|
+
|
|
756
|
+
export type ProjectDocumentDefaultRow = InferSelectModel<typeof projectDocumentDefaults>;
|
|
757
|
+
|
|
758
|
+
// ── User-Defined Tables (structured data) ───────────────────────────────
|
|
759
|
+
|
|
760
|
+
export const userTables = sqliteTable(
|
|
761
|
+
"user_tables",
|
|
762
|
+
{
|
|
763
|
+
id: text("id").primaryKey(),
|
|
764
|
+
projectId: text("project_id").references(() => projects.id),
|
|
765
|
+
name: text("name").notNull(),
|
|
766
|
+
description: text("description"),
|
|
767
|
+
/** JSON array of column definitions — denormalized for fast reads */
|
|
768
|
+
columnSchema: text("column_schema").notNull().default("[]"),
|
|
769
|
+
/** Denormalized row count for list views */
|
|
770
|
+
rowCount: integer("row_count").default(0).notNull(),
|
|
771
|
+
/** How this table was created */
|
|
772
|
+
source: text("source", {
|
|
773
|
+
enum: ["manual", "imported", "agent", "template"],
|
|
774
|
+
})
|
|
775
|
+
.default("manual")
|
|
776
|
+
.notNull(),
|
|
777
|
+
/** Template ID if created from a template */
|
|
778
|
+
templateId: text("template_id"),
|
|
779
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
780
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
|
|
781
|
+
},
|
|
782
|
+
(table) => [
|
|
783
|
+
index("idx_user_tables_project_id").on(table.projectId),
|
|
784
|
+
index("idx_user_tables_source").on(table.source),
|
|
785
|
+
]
|
|
786
|
+
);
|
|
787
|
+
|
|
788
|
+
export const userTableColumns = sqliteTable(
|
|
789
|
+
"user_table_columns",
|
|
790
|
+
{
|
|
791
|
+
id: text("id").primaryKey(),
|
|
792
|
+
tableId: text("table_id")
|
|
793
|
+
.references(() => userTables.id)
|
|
794
|
+
.notNull(),
|
|
795
|
+
name: text("name").notNull(),
|
|
796
|
+
displayName: text("display_name").notNull(),
|
|
797
|
+
dataType: text("data_type", {
|
|
798
|
+
enum: [
|
|
799
|
+
"text",
|
|
800
|
+
"number",
|
|
801
|
+
"date",
|
|
802
|
+
"boolean",
|
|
803
|
+
"select",
|
|
804
|
+
"url",
|
|
805
|
+
"email",
|
|
806
|
+
"relation",
|
|
807
|
+
"computed",
|
|
808
|
+
],
|
|
809
|
+
}).notNull(),
|
|
810
|
+
position: integer("position").notNull(),
|
|
811
|
+
required: integer("required", { mode: "boolean" }).default(false).notNull(),
|
|
812
|
+
defaultValue: text("default_value"),
|
|
813
|
+
/** JSON config for type-specific settings (select options, formula, relation target, etc.) */
|
|
814
|
+
config: text("config"),
|
|
815
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
816
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
|
|
817
|
+
},
|
|
818
|
+
(table) => [
|
|
819
|
+
index("idx_user_table_columns_table_id").on(table.tableId),
|
|
820
|
+
index("idx_user_table_columns_position").on(table.tableId, table.position),
|
|
821
|
+
]
|
|
822
|
+
);
|
|
823
|
+
|
|
824
|
+
export const userTableRows = sqliteTable(
|
|
825
|
+
"user_table_rows",
|
|
826
|
+
{
|
|
827
|
+
id: text("id").primaryKey(),
|
|
828
|
+
tableId: text("table_id")
|
|
829
|
+
.references(() => userTables.id)
|
|
830
|
+
.notNull(),
|
|
831
|
+
/** JSON object with column values keyed by column name */
|
|
832
|
+
data: text("data").notNull().default("{}"),
|
|
833
|
+
position: integer("position").notNull(),
|
|
834
|
+
/** Who created this row: 'user' or agent profile ID */
|
|
835
|
+
createdBy: text("created_by").default("user"),
|
|
836
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
837
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
|
|
838
|
+
},
|
|
839
|
+
(table) => [
|
|
840
|
+
index("idx_user_table_rows_table_id").on(table.tableId),
|
|
841
|
+
index("idx_user_table_rows_position").on(table.tableId, table.position),
|
|
842
|
+
]
|
|
843
|
+
);
|
|
844
|
+
|
|
845
|
+
export const userTableViews = sqliteTable(
|
|
846
|
+
"user_table_views",
|
|
847
|
+
{
|
|
848
|
+
id: text("id").primaryKey(),
|
|
849
|
+
tableId: text("table_id")
|
|
850
|
+
.references(() => userTables.id)
|
|
851
|
+
.notNull(),
|
|
852
|
+
name: text("name").notNull(),
|
|
853
|
+
type: text("type", { enum: ["grid", "chart", "joined"] })
|
|
854
|
+
.default("grid")
|
|
855
|
+
.notNull(),
|
|
856
|
+
/** JSON config: filters, sorting, column visibility, chart config, join config */
|
|
857
|
+
config: text("config"),
|
|
858
|
+
isDefault: integer("is_default", { mode: "boolean" })
|
|
859
|
+
.default(false)
|
|
860
|
+
.notNull(),
|
|
861
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
862
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
|
|
863
|
+
},
|
|
864
|
+
(table) => [
|
|
865
|
+
index("idx_user_table_views_table_id").on(table.tableId),
|
|
866
|
+
]
|
|
867
|
+
);
|
|
868
|
+
|
|
869
|
+
export const userTableRelationships = sqliteTable(
|
|
870
|
+
"user_table_relationships",
|
|
871
|
+
{
|
|
872
|
+
id: text("id").primaryKey(),
|
|
873
|
+
fromTableId: text("from_table_id")
|
|
874
|
+
.references(() => userTables.id)
|
|
875
|
+
.notNull(),
|
|
876
|
+
fromColumn: text("from_column").notNull(),
|
|
877
|
+
toTableId: text("to_table_id")
|
|
878
|
+
.references(() => userTables.id)
|
|
879
|
+
.notNull(),
|
|
880
|
+
toColumn: text("to_column").notNull(),
|
|
881
|
+
relationshipType: text("relationship_type", {
|
|
882
|
+
enum: ["one_to_one", "one_to_many", "many_to_many"],
|
|
883
|
+
}).notNull(),
|
|
884
|
+
/** JSON config for display column, cascade behavior, etc. */
|
|
885
|
+
config: text("config"),
|
|
886
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
887
|
+
},
|
|
888
|
+
(table) => [
|
|
889
|
+
index("idx_user_table_rels_from").on(table.fromTableId),
|
|
890
|
+
index("idx_user_table_rels_to").on(table.toTableId),
|
|
891
|
+
]
|
|
892
|
+
);
|
|
893
|
+
|
|
894
|
+
export const userTableTemplates = sqliteTable(
|
|
895
|
+
"user_table_templates",
|
|
896
|
+
{
|
|
897
|
+
id: text("id").primaryKey(),
|
|
898
|
+
name: text("name").notNull(),
|
|
899
|
+
description: text("description"),
|
|
900
|
+
category: text("category", {
|
|
901
|
+
enum: ["business", "personal", "pm", "finance", "content"],
|
|
902
|
+
}).notNull(),
|
|
903
|
+
/** JSON array of column definitions */
|
|
904
|
+
columnSchema: text("column_schema").notNull(),
|
|
905
|
+
/** JSON array of sample row data */
|
|
906
|
+
sampleData: text("sample_data"),
|
|
907
|
+
scope: text("scope", { enum: ["system", "user"] })
|
|
908
|
+
.default("system")
|
|
909
|
+
.notNull(),
|
|
910
|
+
icon: text("icon"),
|
|
911
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
912
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
|
|
913
|
+
},
|
|
914
|
+
(table) => [
|
|
915
|
+
index("idx_user_table_templates_category").on(table.category),
|
|
916
|
+
index("idx_user_table_templates_scope").on(table.scope),
|
|
917
|
+
]
|
|
918
|
+
);
|
|
919
|
+
|
|
920
|
+
export const userTableImports = sqliteTable(
|
|
921
|
+
"user_table_imports",
|
|
922
|
+
{
|
|
923
|
+
id: text("id").primaryKey(),
|
|
924
|
+
tableId: text("table_id")
|
|
925
|
+
.references(() => userTables.id)
|
|
926
|
+
.notNull(),
|
|
927
|
+
documentId: text("document_id").references(() => documents.id),
|
|
928
|
+
/** Number of rows imported */
|
|
929
|
+
rowCount: integer("row_count").default(0).notNull(),
|
|
930
|
+
/** Number of rows that failed validation */
|
|
931
|
+
errorCount: integer("error_count").default(0).notNull(),
|
|
932
|
+
/** JSON array of error details */
|
|
933
|
+
errors: text("errors"),
|
|
934
|
+
status: text("status", { enum: ["pending", "completed", "failed"] })
|
|
935
|
+
.default("pending")
|
|
936
|
+
.notNull(),
|
|
937
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
938
|
+
},
|
|
939
|
+
(table) => [
|
|
940
|
+
index("idx_user_table_imports_table_id").on(table.tableId),
|
|
941
|
+
]
|
|
942
|
+
);
|
|
943
|
+
|
|
944
|
+
// ── Table Junction Tables ───────────────────────────────────────────────
|
|
945
|
+
|
|
946
|
+
export const tableDocumentInputs = sqliteTable(
|
|
947
|
+
"table_document_inputs",
|
|
948
|
+
{
|
|
949
|
+
id: text("id").primaryKey(),
|
|
950
|
+
tableId: text("table_id")
|
|
951
|
+
.references(() => userTables.id)
|
|
952
|
+
.notNull(),
|
|
953
|
+
documentId: text("document_id")
|
|
954
|
+
.references(() => documents.id)
|
|
955
|
+
.notNull(),
|
|
956
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
957
|
+
},
|
|
958
|
+
(table) => [
|
|
959
|
+
index("idx_tdi_table").on(table.tableId),
|
|
960
|
+
uniqueIndex("idx_tdi_table_doc").on(table.tableId, table.documentId),
|
|
961
|
+
]
|
|
962
|
+
);
|
|
963
|
+
|
|
964
|
+
export const taskTableInputs = sqliteTable(
|
|
965
|
+
"task_table_inputs",
|
|
966
|
+
{
|
|
967
|
+
id: text("id").primaryKey(),
|
|
968
|
+
taskId: text("task_id")
|
|
969
|
+
.references(() => tasks.id)
|
|
970
|
+
.notNull(),
|
|
971
|
+
tableId: text("table_id")
|
|
972
|
+
.references(() => userTables.id)
|
|
973
|
+
.notNull(),
|
|
974
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
975
|
+
},
|
|
976
|
+
(table) => [
|
|
977
|
+
index("idx_tti_task").on(table.taskId),
|
|
978
|
+
uniqueIndex("idx_tti_task_table").on(table.taskId, table.tableId),
|
|
979
|
+
]
|
|
980
|
+
);
|
|
981
|
+
|
|
982
|
+
export const workflowTableInputs = sqliteTable(
|
|
983
|
+
"workflow_table_inputs",
|
|
984
|
+
{
|
|
985
|
+
id: text("id").primaryKey(),
|
|
986
|
+
workflowId: text("workflow_id")
|
|
987
|
+
.references(() => workflows.id)
|
|
988
|
+
.notNull(),
|
|
989
|
+
tableId: text("table_id")
|
|
990
|
+
.references(() => userTables.id)
|
|
991
|
+
.notNull(),
|
|
992
|
+
/** null = table available to all steps; set = scoped to specific step */
|
|
993
|
+
stepId: text("step_id"),
|
|
994
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
995
|
+
},
|
|
996
|
+
(table) => [
|
|
997
|
+
index("idx_wti_workflow").on(table.workflowId),
|
|
998
|
+
uniqueIndex("idx_wti_workflow_table_step").on(
|
|
999
|
+
table.workflowId,
|
|
1000
|
+
table.tableId,
|
|
1001
|
+
table.stepId
|
|
1002
|
+
),
|
|
1003
|
+
]
|
|
1004
|
+
);
|
|
1005
|
+
|
|
1006
|
+
export const scheduleTableInputs = sqliteTable(
|
|
1007
|
+
"schedule_table_inputs",
|
|
1008
|
+
{
|
|
1009
|
+
id: text("id").primaryKey(),
|
|
1010
|
+
scheduleId: text("schedule_id")
|
|
1011
|
+
.references(() => schedules.id)
|
|
1012
|
+
.notNull(),
|
|
1013
|
+
tableId: text("table_id")
|
|
1014
|
+
.references(() => userTables.id)
|
|
1015
|
+
.notNull(),
|
|
1016
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
1017
|
+
},
|
|
1018
|
+
(table) => [
|
|
1019
|
+
index("idx_sti_schedule").on(table.scheduleId),
|
|
1020
|
+
uniqueIndex("idx_sti_schedule_table").on(
|
|
1021
|
+
table.scheduleId,
|
|
1022
|
+
table.tableId
|
|
1023
|
+
),
|
|
1024
|
+
]
|
|
1025
|
+
);
|
|
1026
|
+
|
|
1027
|
+
// ── Table Workflow Triggers ──────────────────────────────────────────
|
|
1028
|
+
|
|
1029
|
+
export const userTableTriggers = sqliteTable(
|
|
1030
|
+
"user_table_triggers",
|
|
1031
|
+
{
|
|
1032
|
+
id: text("id").primaryKey(),
|
|
1033
|
+
tableId: text("table_id")
|
|
1034
|
+
.references(() => userTables.id)
|
|
1035
|
+
.notNull(),
|
|
1036
|
+
name: text("name").notNull(),
|
|
1037
|
+
triggerEvent: text("trigger_event", {
|
|
1038
|
+
enum: ["row_added", "row_updated", "row_deleted"],
|
|
1039
|
+
}).notNull(),
|
|
1040
|
+
/** JSON condition using filter format (null = always fire) */
|
|
1041
|
+
condition: text("condition"),
|
|
1042
|
+
actionType: text("action_type", {
|
|
1043
|
+
enum: ["run_workflow", "create_task"],
|
|
1044
|
+
}).notNull(),
|
|
1045
|
+
/** JSON config: { workflowId } or { title, description, projectId } */
|
|
1046
|
+
actionConfig: text("action_config").notNull(),
|
|
1047
|
+
status: text("status", { enum: ["active", "paused"] })
|
|
1048
|
+
.default("active")
|
|
1049
|
+
.notNull(),
|
|
1050
|
+
fireCount: integer("fire_count").default(0).notNull(),
|
|
1051
|
+
lastFiredAt: integer("last_fired_at", { mode: "timestamp" }),
|
|
1052
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
1053
|
+
updatedAt: integer("updated_at", { mode: "timestamp" }).notNull(),
|
|
1054
|
+
},
|
|
1055
|
+
(table) => [
|
|
1056
|
+
index("idx_user_table_triggers_table_id").on(table.tableId),
|
|
1057
|
+
index("idx_user_table_triggers_status").on(table.status),
|
|
1058
|
+
]
|
|
1059
|
+
);
|
|
1060
|
+
|
|
1061
|
+
// ── Table Row Version History ────────────────────────────────────────
|
|
1062
|
+
|
|
1063
|
+
export const userTableRowHistory = sqliteTable(
|
|
1064
|
+
"user_table_row_history",
|
|
1065
|
+
{
|
|
1066
|
+
id: text("id").primaryKey(),
|
|
1067
|
+
rowId: text("row_id").notNull(),
|
|
1068
|
+
tableId: text("table_id")
|
|
1069
|
+
.references(() => userTables.id)
|
|
1070
|
+
.notNull(),
|
|
1071
|
+
/** JSON snapshot of the row data before the change */
|
|
1072
|
+
previousData: text("previous_data").notNull(),
|
|
1073
|
+
changedBy: text("changed_by").default("user"),
|
|
1074
|
+
changeType: text("change_type", { enum: ["update", "delete"] }).notNull(),
|
|
1075
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
1076
|
+
},
|
|
1077
|
+
(table) => [
|
|
1078
|
+
index("idx_row_history_row_id").on(table.rowId),
|
|
1079
|
+
index("idx_row_history_table_id").on(table.tableId),
|
|
1080
|
+
index("idx_row_history_created_at").on(table.createdAt),
|
|
1081
|
+
]
|
|
1082
|
+
);
|
|
1083
|
+
|
|
681
1084
|
// Shared types derived from schema — use these in components instead of `as any`
|
|
682
1085
|
export type ProjectRow = InferSelectModel<typeof projects>;
|
|
683
1086
|
export type TaskRow = InferSelectModel<typeof tasks>;
|
|
@@ -703,3 +1106,45 @@ export type ReadingProgressRow = InferSelectModel<typeof readingProgress>;
|
|
|
703
1106
|
export type BookmarkRow = InferSelectModel<typeof bookmarks>;
|
|
704
1107
|
export type RepoImportRow = InferSelectModel<typeof repoImports>;
|
|
705
1108
|
export type AgentMessageRow = InferSelectModel<typeof agentMessages>;
|
|
1109
|
+
export type UserTableRow = InferSelectModel<typeof userTables>;
|
|
1110
|
+
export type UserTableColumnRow = InferSelectModel<typeof userTableColumns>;
|
|
1111
|
+
export type UserTableRowRow = InferSelectModel<typeof userTableRows>;
|
|
1112
|
+
export type UserTableViewRow = InferSelectModel<typeof userTableViews>;
|
|
1113
|
+
export type UserTableRelationshipRow = InferSelectModel<typeof userTableRelationships>;
|
|
1114
|
+
export type UserTableTemplateRow = InferSelectModel<typeof userTableTemplates>;
|
|
1115
|
+
export type UserTableImportRow = InferSelectModel<typeof userTableImports>;
|
|
1116
|
+
export type TableDocumentInputRow = InferSelectModel<typeof tableDocumentInputs>;
|
|
1117
|
+
export type TaskTableInputRow = InferSelectModel<typeof taskTableInputs>;
|
|
1118
|
+
export type WorkflowTableInputRow = InferSelectModel<typeof workflowTableInputs>;
|
|
1119
|
+
export type ScheduleTableInputRow = InferSelectModel<typeof scheduleTableInputs>;
|
|
1120
|
+
export type UserTableTriggerRow = InferSelectModel<typeof userTableTriggers>;
|
|
1121
|
+
export type UserTableRowHistoryRow = InferSelectModel<typeof userTableRowHistory>;
|
|
1122
|
+
|
|
1123
|
+
// ── Snapshots ──────────────────────────────────────────────────────────
|
|
1124
|
+
|
|
1125
|
+
export const snapshots = sqliteTable(
|
|
1126
|
+
"snapshots",
|
|
1127
|
+
{
|
|
1128
|
+
id: text("id").primaryKey(),
|
|
1129
|
+
label: text("label").notNull(),
|
|
1130
|
+
type: text("type", { enum: ["manual", "auto"] })
|
|
1131
|
+
.default("manual")
|
|
1132
|
+
.notNull(),
|
|
1133
|
+
status: text("status", { enum: ["in_progress", "completed", "failed"] })
|
|
1134
|
+
.default("in_progress")
|
|
1135
|
+
.notNull(),
|
|
1136
|
+
filePath: text("file_path").notNull(),
|
|
1137
|
+
sizeBytes: integer("size_bytes").default(0).notNull(),
|
|
1138
|
+
dbSizeBytes: integer("db_size_bytes").default(0).notNull(),
|
|
1139
|
+
filesSizeBytes: integer("files_size_bytes").default(0).notNull(),
|
|
1140
|
+
fileCount: integer("file_count").default(0).notNull(),
|
|
1141
|
+
error: text("error"),
|
|
1142
|
+
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
|
|
1143
|
+
},
|
|
1144
|
+
(table) => [
|
|
1145
|
+
index("idx_snapshots_type").on(table.type),
|
|
1146
|
+
index("idx_snapshots_created_at").on(table.createdAt),
|
|
1147
|
+
]
|
|
1148
|
+
);
|
|
1149
|
+
|
|
1150
|
+
export type SnapshotRow = InferSelectModel<typeof snapshots>;
|
package/src/lib/docs/reader.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { readFileSync, readdirSync, existsSync } from "fs";
|
|
2
2
|
import { join, basename } from "path";
|
|
3
3
|
import type { DocManifest, ParsedDoc } from "./types";
|
|
4
|
+
import { getAppRoot } from "../utils/app-root";
|
|
4
5
|
|
|
5
6
|
/** Resolve the docs directory relative to this source file (npx-safe) */
|
|
6
7
|
function docsDir(): string {
|
|
7
|
-
|
|
8
|
-
// src/lib/docs/ → project root → docs/
|
|
9
|
-
return join(dir, "..", "..", "..", "docs");
|
|
8
|
+
return join(getAppRoot(import.meta.dirname, 3), "docs");
|
|
10
9
|
}
|
|
11
10
|
|
|
12
11
|
/** Read and parse docs/manifest.json */
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { db } from "@/lib/db";
|
|
7
|
-
import { documents } from "@/lib/db/schema";
|
|
8
|
-
import { and, eq } from "drizzle-orm";
|
|
7
|
+
import { documents, workflowDocumentInputs } from "@/lib/db/schema";
|
|
8
|
+
import { and, eq, inArray, isNull } from "drizzle-orm";
|
|
9
9
|
import type { DocumentRow } from "@/lib/db/schema";
|
|
10
10
|
|
|
11
11
|
const MAX_INLINE_TEXT = 10_000;
|
|
@@ -114,3 +114,76 @@ export async function buildWorkflowDocumentContext(
|
|
|
114
114
|
return null;
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Build document context from the workflow document pool (junction table).
|
|
120
|
+
* Queries workflow_document_inputs for documents bound to this workflow,
|
|
121
|
+
* optionally scoped to a specific step. Returns null if no pool documents.
|
|
122
|
+
*
|
|
123
|
+
* Includes both workflow-level bindings (stepId=null) and step-specific bindings.
|
|
124
|
+
*/
|
|
125
|
+
export async function buildPoolDocumentContext(
|
|
126
|
+
workflowId: string,
|
|
127
|
+
stepId?: string
|
|
128
|
+
): Promise<string | null> {
|
|
129
|
+
try {
|
|
130
|
+
// Get workflow-level (stepId=null) bindings — available to all steps
|
|
131
|
+
const globalBindings = await db
|
|
132
|
+
.select({ documentId: workflowDocumentInputs.documentId })
|
|
133
|
+
.from(workflowDocumentInputs)
|
|
134
|
+
.where(
|
|
135
|
+
and(
|
|
136
|
+
eq(workflowDocumentInputs.workflowId, workflowId),
|
|
137
|
+
isNull(workflowDocumentInputs.stepId)
|
|
138
|
+
)
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
// If a specific step, also get step-scoped bindings
|
|
142
|
+
let stepBindings: { documentId: string }[] = [];
|
|
143
|
+
if (stepId) {
|
|
144
|
+
stepBindings = await db
|
|
145
|
+
.select({ documentId: workflowDocumentInputs.documentId })
|
|
146
|
+
.from(workflowDocumentInputs)
|
|
147
|
+
.where(
|
|
148
|
+
and(
|
|
149
|
+
eq(workflowDocumentInputs.workflowId, workflowId),
|
|
150
|
+
eq(workflowDocumentInputs.stepId, stepId)
|
|
151
|
+
)
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Deduplicate document IDs
|
|
156
|
+
const docIdSet = new Set<string>();
|
|
157
|
+
for (const b of [...globalBindings, ...stepBindings]) {
|
|
158
|
+
docIdSet.add(b.documentId);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (docIdSet.size === 0) return null;
|
|
162
|
+
|
|
163
|
+
const docs = await db
|
|
164
|
+
.select()
|
|
165
|
+
.from(documents)
|
|
166
|
+
.where(inArray(documents.id, [...docIdSet]));
|
|
167
|
+
|
|
168
|
+
if (docs.length === 0) return null;
|
|
169
|
+
|
|
170
|
+
const sections = docs.map((doc, i) => formatDocument(doc, i));
|
|
171
|
+
let result = sections.join("\n\n");
|
|
172
|
+
|
|
173
|
+
if (result.length > MAX_WORKFLOW_DOC_CONTEXT) {
|
|
174
|
+
result = result.slice(0, MAX_WORKFLOW_DOC_CONTEXT);
|
|
175
|
+
result += `\n\n(Pool document context truncated at ${MAX_WORKFLOW_DOC_CONTEXT} chars — use Read tool for full content)`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return [
|
|
179
|
+
"--- Workflow Pool Documents ---",
|
|
180
|
+
"",
|
|
181
|
+
result,
|
|
182
|
+
"",
|
|
183
|
+
"--- End Workflow Pool Documents ---",
|
|
184
|
+
].join("\n");
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error("[context-builder] Failed to build pool document context:", error);
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve document selectors against the project document pool.
|
|
3
|
+
* Used at workflow creation time for auto-discovery of relevant documents.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { db } from "@/lib/db";
|
|
7
|
+
import { documents, workflows, tasks } from "@/lib/db/schema";
|
|
8
|
+
import { and, eq, desc, like, inArray } from "drizzle-orm";
|
|
9
|
+
import type { DocumentRow } from "@/lib/db/schema";
|
|
10
|
+
import type { DocumentSelector } from "@/lib/workflows/types";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolve a DocumentSelector against the project pool, returning matching documents.
|
|
14
|
+
* Used for auto-discovery at workflow creation time (not at execution time).
|
|
15
|
+
*/
|
|
16
|
+
export async function resolveDocumentSelector(
|
|
17
|
+
projectId: string,
|
|
18
|
+
selector: DocumentSelector
|
|
19
|
+
): Promise<DocumentRow[]> {
|
|
20
|
+
const conditions = [eq(documents.projectId, projectId)];
|
|
21
|
+
|
|
22
|
+
if (selector.direction) {
|
|
23
|
+
conditions.push(eq(documents.direction, selector.direction));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (selector.category) {
|
|
27
|
+
conditions.push(eq(documents.category, selector.category));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (selector.mimeType) {
|
|
31
|
+
conditions.push(eq(documents.mimeType, selector.mimeType));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (selector.namePattern) {
|
|
35
|
+
// Convert glob pattern to SQL LIKE: * → %, ? → _
|
|
36
|
+
const likePattern = selector.namePattern
|
|
37
|
+
.replace(/\*/g, "%")
|
|
38
|
+
.replace(/\?/g, "_");
|
|
39
|
+
conditions.push(like(documents.originalName, likePattern));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Filter by source workflow (via task → workflow relationship)
|
|
43
|
+
if (selector.fromWorkflowId) {
|
|
44
|
+
const workflowTaskIds = await db
|
|
45
|
+
.select({ id: tasks.id })
|
|
46
|
+
.from(tasks)
|
|
47
|
+
.where(eq(tasks.workflowId, selector.fromWorkflowId));
|
|
48
|
+
|
|
49
|
+
const taskIds = workflowTaskIds.map((t) => t.id);
|
|
50
|
+
if (taskIds.length === 0) return [];
|
|
51
|
+
conditions.push(inArray(documents.taskId, taskIds));
|
|
52
|
+
} else if (selector.fromWorkflowName) {
|
|
53
|
+
// Look up workflow by name, then get its task IDs
|
|
54
|
+
const matchingWorkflows = await db
|
|
55
|
+
.select({ id: workflows.id })
|
|
56
|
+
.from(workflows)
|
|
57
|
+
.where(
|
|
58
|
+
and(
|
|
59
|
+
eq(workflows.projectId, projectId),
|
|
60
|
+
like(workflows.name, `%${selector.fromWorkflowName}%`)
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
if (matchingWorkflows.length === 0) return [];
|
|
65
|
+
|
|
66
|
+
const wfIds = matchingWorkflows.map((w) => w.id);
|
|
67
|
+
const workflowTaskIds = await db
|
|
68
|
+
.select({ id: tasks.id })
|
|
69
|
+
.from(tasks)
|
|
70
|
+
.where(inArray(tasks.workflowId, wfIds));
|
|
71
|
+
|
|
72
|
+
const taskIds = workflowTaskIds.map((t) => t.id);
|
|
73
|
+
if (taskIds.length === 0) return [];
|
|
74
|
+
conditions.push(inArray(documents.taskId, taskIds));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Only return ready documents (processed and available)
|
|
78
|
+
conditions.push(eq(documents.status, "ready"));
|
|
79
|
+
|
|
80
|
+
let query = db
|
|
81
|
+
.select()
|
|
82
|
+
.from(documents)
|
|
83
|
+
.where(and(...conditions))
|
|
84
|
+
.orderBy(desc(documents.createdAt));
|
|
85
|
+
|
|
86
|
+
if (selector.latest) {
|
|
87
|
+
query = query.limit(selector.latest) as typeof query;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return query;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Get all output documents from completed workflows in a project.
|
|
95
|
+
* Useful for browsing the project document pool.
|
|
96
|
+
*/
|
|
97
|
+
export async function getProjectDocumentPool(
|
|
98
|
+
projectId: string,
|
|
99
|
+
options?: { direction?: "input" | "output"; search?: string }
|
|
100
|
+
): Promise<DocumentRow[]> {
|
|
101
|
+
const conditions = [
|
|
102
|
+
eq(documents.projectId, projectId),
|
|
103
|
+
eq(documents.status, "ready"),
|
|
104
|
+
];
|
|
105
|
+
|
|
106
|
+
if (options?.direction) {
|
|
107
|
+
conditions.push(eq(documents.direction, options.direction));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (options?.search) {
|
|
111
|
+
conditions.push(like(documents.originalName, `%${options.search}%`));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return db
|
|
115
|
+
.select()
|
|
116
|
+
.from(documents)
|
|
117
|
+
.where(and(...conditions))
|
|
118
|
+
.orderBy(desc(documents.createdAt));
|
|
119
|
+
}
|
|
@@ -6,7 +6,8 @@ export async function processSpreadsheet(filePath: string): Promise<ProcessorRes
|
|
|
6
6
|
const ExcelJS = await import("exceljs");
|
|
7
7
|
const workbook = new ExcelJS.Workbook();
|
|
8
8
|
const buffer = await readFile(filePath);
|
|
9
|
-
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
+
await workbook.xlsx.load(buffer as any);
|
|
10
11
|
|
|
11
12
|
const sheets: string[] = [];
|
|
12
13
|
workbook.eachSheet((worksheet) => {
|