@manablox/db 0.1.0 → 0.3.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 -0
- package/dist/index-Cyf_N5K3.d.ts +658 -0
- package/dist/index-rZ24t-Ln.d.ts +4338 -0
- package/dist/index.d.ts +123 -0
- package/dist/index.js +60 -0
- package/dist/repositories-DYjzuuF6.js +1533 -0
- package/dist/rolldown-runtime-D7D4PA-g.js +13 -0
- package/dist/schema-Bb4p16Yz.js +539 -0
- package/dist/schema.d.ts +2 -0
- package/dist/schema.js +2 -0
- package/dist/testing.d.ts +77 -0
- package/dist/testing.js +217 -0
- package/migrations/0005_menus.sql +44 -0
- package/migrations/0006_roles.sql +13 -0
- package/migrations/0007_apikey-permissions.sql +4 -0
- package/migrations/0008_workflows.sql +49 -0
- package/migrations/meta/0005_snapshot.json +2504 -0
- package/migrations/meta/0006_snapshot.json +2605 -0
- package/migrations/meta/0007_snapshot.json +2605 -0
- package/migrations/meta/0008_snapshot.json +2986 -0
- package/migrations/meta/_journal.json +28 -0
- package/package.json +22 -9
- package/drizzle.config.ts +0 -11
- package/src/bootstrap.ts +0 -13
- package/src/cli/migrate.ts +0 -24
- package/src/client.ts +0 -37
- package/src/columns.ts +0 -39
- package/src/index.ts +0 -13
- package/src/query.ts +0 -217
- package/src/repositories/asset-usage.ts +0 -166
- package/src/repositories/asset.ts +0 -189
- package/src/repositories/content-type.ts +0 -116
- package/src/repositories/content.ts +0 -758
- package/src/repositories/index.ts +0 -28
- package/src/repositories/space.ts +0 -78
- package/src/repositories/user.ts +0 -134
- package/src/schema.ts +0 -513
- package/test/asset-usage.test.ts +0 -101
- package/test/helpers.ts +0 -153
- package/test/publish.test.ts +0 -102
- package/test/query.test.ts +0 -140
- package/test/tree.test.ts +0 -188
- package/tsconfig.json +0 -4
- package/vitest.config.ts +0 -12
package/dist/testing.js
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import { _ as applyBootstrapSql, g as createDatabase, t as createRepositories } from "./repositories-DYjzuuF6.js";
|
|
2
|
+
import postgres from "postgres";
|
|
3
|
+
import { ContentTypeRegistry, FieldTypeRegistry, defineContentType, defineFieldType } from "@manablox/core";
|
|
4
|
+
import { dirname, join, resolve } from "node:path";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import { migrate } from "drizzle-orm/postgres-js/migrator";
|
|
7
|
+
import { createHash } from "node:crypto";
|
|
8
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
9
|
+
//#region src/testing-fixtures.ts
|
|
10
|
+
/**
|
|
11
|
+
* Fixtures for a repository-level test: two plain field types, a `page` and a `folder`
|
|
12
|
+
* content type, a migrated database with one space, and a node maker. Shared through
|
|
13
|
+
* `@manablox/db/testing` so a suite in another package can start from the same world.
|
|
14
|
+
*/
|
|
15
|
+
const anySchema = () => ({ "~standard": {
|
|
16
|
+
version: 1,
|
|
17
|
+
vendor: "test",
|
|
18
|
+
validate: (value) => ({ value })
|
|
19
|
+
} });
|
|
20
|
+
const stringField = defineFieldType({
|
|
21
|
+
name: "string",
|
|
22
|
+
label: "Text",
|
|
23
|
+
settingsSchema: anySchema(),
|
|
24
|
+
valueSchema: () => anySchema(),
|
|
25
|
+
defaultValue: () => "",
|
|
26
|
+
storage: {
|
|
27
|
+
kind: "jsonb",
|
|
28
|
+
index: "btree"
|
|
29
|
+
},
|
|
30
|
+
filters: [
|
|
31
|
+
"eq",
|
|
32
|
+
"neq",
|
|
33
|
+
"contains",
|
|
34
|
+
"startsWith",
|
|
35
|
+
"in",
|
|
36
|
+
"isNull",
|
|
37
|
+
"isNotNull"
|
|
38
|
+
],
|
|
39
|
+
graphql: { type: {
|
|
40
|
+
kind: "scalar",
|
|
41
|
+
name: "String"
|
|
42
|
+
} },
|
|
43
|
+
search: (value) => typeof value === "string" ? value : null,
|
|
44
|
+
admin: { input: "string" }
|
|
45
|
+
});
|
|
46
|
+
const numberField = defineFieldType({
|
|
47
|
+
name: "number",
|
|
48
|
+
label: "Number",
|
|
49
|
+
settingsSchema: anySchema(),
|
|
50
|
+
valueSchema: () => anySchema(),
|
|
51
|
+
defaultValue: () => 0,
|
|
52
|
+
storage: {
|
|
53
|
+
kind: "jsonb",
|
|
54
|
+
index: "btree"
|
|
55
|
+
},
|
|
56
|
+
filters: [
|
|
57
|
+
"eq",
|
|
58
|
+
"lt",
|
|
59
|
+
"lte",
|
|
60
|
+
"gt",
|
|
61
|
+
"gte"
|
|
62
|
+
],
|
|
63
|
+
graphql: { type: {
|
|
64
|
+
kind: "scalar",
|
|
65
|
+
name: "Float"
|
|
66
|
+
} },
|
|
67
|
+
admin: { input: "number" }
|
|
68
|
+
});
|
|
69
|
+
/** Spins up an isolated database per suite, migrated from the real migration files. */
|
|
70
|
+
async function createRepositoryContext(name) {
|
|
71
|
+
const database = await createTestDatabase(name);
|
|
72
|
+
const queries = [];
|
|
73
|
+
const handle = createDatabase({
|
|
74
|
+
url: database.url,
|
|
75
|
+
max: 4
|
|
76
|
+
}, { onQuery: (query) => queries.push(query) });
|
|
77
|
+
const fieldTypes = new FieldTypeRegistry();
|
|
78
|
+
fieldTypes.register(stringField);
|
|
79
|
+
fieldTypes.register(numberField);
|
|
80
|
+
const page = defineContentType({
|
|
81
|
+
name: "page",
|
|
82
|
+
fields: [{
|
|
83
|
+
name: "body",
|
|
84
|
+
type: "string"
|
|
85
|
+
}, {
|
|
86
|
+
name: "weight",
|
|
87
|
+
type: "number"
|
|
88
|
+
}]
|
|
89
|
+
});
|
|
90
|
+
const folder = defineContentType({
|
|
91
|
+
name: "folder",
|
|
92
|
+
hasSlug: false,
|
|
93
|
+
fields: [{
|
|
94
|
+
name: "note",
|
|
95
|
+
type: "string"
|
|
96
|
+
}]
|
|
97
|
+
});
|
|
98
|
+
const registry = new ContentTypeRegistry(fieldTypes);
|
|
99
|
+
registry.setAll([page, folder]);
|
|
100
|
+
registry.validate();
|
|
101
|
+
const repos = createRepositories(handle.db, registry);
|
|
102
|
+
const space = await repos.spaces.create({
|
|
103
|
+
name: "Test",
|
|
104
|
+
machineName: "test",
|
|
105
|
+
url: "http://localhost:3002"
|
|
106
|
+
});
|
|
107
|
+
return {
|
|
108
|
+
handle,
|
|
109
|
+
queries,
|
|
110
|
+
repos,
|
|
111
|
+
registry,
|
|
112
|
+
types: {
|
|
113
|
+
page,
|
|
114
|
+
folder
|
|
115
|
+
},
|
|
116
|
+
spaceId: space.id,
|
|
117
|
+
close: async () => {
|
|
118
|
+
await handle.close();
|
|
119
|
+
await database.drop();
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/** Convenience creator so tests read as tree shapes rather than field soup. */
|
|
124
|
+
async function makeNode(ctx, options) {
|
|
125
|
+
const type = ctx.types[options.type ?? "page"];
|
|
126
|
+
const searchText = Object.values(options.fields ?? {}).filter((value) => typeof value === "string").join(" ");
|
|
127
|
+
return ctx.repos.content.create({
|
|
128
|
+
searchText,
|
|
129
|
+
spaceId: ctx.spaceId,
|
|
130
|
+
typeId: type.id,
|
|
131
|
+
locale: "en",
|
|
132
|
+
parentId: options.parentId ?? null,
|
|
133
|
+
title: options.title,
|
|
134
|
+
slug: options.slug,
|
|
135
|
+
fields: options.fields ?? {},
|
|
136
|
+
hasSlug: type.hasSlug
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
//#endregion
|
|
140
|
+
//#region src/testing.ts
|
|
141
|
+
const MIGRATIONS = resolve(dirname(fileURLToPath(import.meta.url)), "../migrations");
|
|
142
|
+
/**
|
|
143
|
+
* Only ever connected to in order to `create database`: every test file works in its own
|
|
144
|
+
* fresh database and drops it again, so this points at a Postgres *server*, not at data
|
|
145
|
+
* the tests touch. That is why falling back to `DATABASE_URL` is safe — it means the
|
|
146
|
+
* suite runs wherever the app itself runs (a container, CI, the host) without a second
|
|
147
|
+
* variable that has to be kept in step. `TEST_DATABASE_URL` still overrides it.
|
|
148
|
+
*/
|
|
149
|
+
const TEST_ADMIN_URL = process.env.TEST_DATABASE_URL ?? process.env.DATABASE_URL ?? "postgres://manablox:manablox@localhost:5432/manablox";
|
|
150
|
+
/** A fingerprint of the migration files, so a schema change gets a fresh template. */
|
|
151
|
+
function migrationsFingerprint() {
|
|
152
|
+
const hash = createHash("sha1");
|
|
153
|
+
for (const file of readdirSync(MIGRATIONS).sort()) if (file.endsWith(".sql")) hash.update(readFileSync(join(MIGRATIONS, file)));
|
|
154
|
+
return hash.digest("hex").slice(0, 10);
|
|
155
|
+
}
|
|
156
|
+
const TEMPLATE = `manablox_test_template_${migrationsFingerprint()}`;
|
|
157
|
+
/**
|
|
158
|
+
* Makes sure the migrated template exists, once per server.
|
|
159
|
+
*
|
|
160
|
+
* Migrating takes a second or two; `create database … template …` takes milliseconds,
|
|
161
|
+
* so every suite clones the template instead of migrating from scratch. Suites run in
|
|
162
|
+
* parallel across processes, so the check-and-create is serialised on a session-level
|
|
163
|
+
* advisory lock — the second process finds the template already there.
|
|
164
|
+
*/
|
|
165
|
+
async function ensureTemplate(admin) {
|
|
166
|
+
await admin.unsafe(`select pg_advisory_lock(hashtext('${TEMPLATE}'))`);
|
|
167
|
+
try {
|
|
168
|
+
const [row] = await admin.unsafe(`select 1 as found from pg_database where datname = '${TEMPLATE}'`);
|
|
169
|
+
if (row) return;
|
|
170
|
+
await admin.unsafe(`create database "${TEMPLATE}"`);
|
|
171
|
+
const handle = createDatabase({
|
|
172
|
+
url: withDatabase(TEST_ADMIN_URL, TEMPLATE),
|
|
173
|
+
max: 1
|
|
174
|
+
});
|
|
175
|
+
try {
|
|
176
|
+
await applyBootstrapSql(handle.sql);
|
|
177
|
+
await migrate(handle.db, { migrationsFolder: MIGRATIONS });
|
|
178
|
+
} finally {
|
|
179
|
+
await handle.close();
|
|
180
|
+
}
|
|
181
|
+
} finally {
|
|
182
|
+
await admin.unsafe(`select pg_advisory_unlock(hashtext('${TEMPLATE}'))`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
function withDatabase(url, name) {
|
|
186
|
+
return url.replace(/\/[^/?]*(\?.*)?$/, `/${name}$1`);
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* A fresh, migrated database for one test suite, cloned from the template.
|
|
190
|
+
*
|
|
191
|
+
* `prefix` names the suite in `pg_database`, which is what you grep for when a run was
|
|
192
|
+
* interrupted and left a database behind.
|
|
193
|
+
*/
|
|
194
|
+
async function createTestDatabase(prefix) {
|
|
195
|
+
const name = `manablox_test_${prefix}_${Date.now().toString(36)}_${process.pid.toString(36)}`;
|
|
196
|
+
const admin = postgres(TEST_ADMIN_URL, { max: 1 });
|
|
197
|
+
try {
|
|
198
|
+
await ensureTemplate(admin);
|
|
199
|
+
await admin.unsafe(`create database "${name}" template "${TEMPLATE}"`);
|
|
200
|
+
} finally {
|
|
201
|
+
await admin.end();
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
name,
|
|
205
|
+
url: withDatabase(TEST_ADMIN_URL, name),
|
|
206
|
+
drop: async () => {
|
|
207
|
+
const cleanup = postgres(TEST_ADMIN_URL, { max: 1 });
|
|
208
|
+
try {
|
|
209
|
+
await cleanup.unsafe(`drop database if exists "${name}" with (force)`);
|
|
210
|
+
} finally {
|
|
211
|
+
await cleanup.end();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
//#endregion
|
|
217
|
+
export { TEST_ADMIN_URL, createRepositoryContext, createTestDatabase, makeNode, numberField, stringField, withDatabase };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
CREATE TABLE "menu_items" (
|
|
2
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
3
|
+
"menu_id" uuid NOT NULL,
|
|
4
|
+
"parent_id" uuid,
|
|
5
|
+
"position" integer DEFAULT 0 NOT NULL,
|
|
6
|
+
"localization_id" uuid,
|
|
7
|
+
"label" text,
|
|
8
|
+
"url" text
|
|
9
|
+
);
|
|
10
|
+
--> statement-breakpoint
|
|
11
|
+
CREATE TABLE "menus" (
|
|
12
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
13
|
+
"space_id" uuid NOT NULL,
|
|
14
|
+
"name" text NOT NULL,
|
|
15
|
+
"machine_name" text NOT NULL,
|
|
16
|
+
"description" text,
|
|
17
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
18
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
19
|
+
CONSTRAINT "menus_space_machine_name_key" UNIQUE("space_id","machine_name")
|
|
20
|
+
);
|
|
21
|
+
--> statement-breakpoint
|
|
22
|
+
ALTER TABLE "menu_items" ADD CONSTRAINT "menu_items_menu_id_menus_id_fk" FOREIGN KEY ("menu_id") REFERENCES "public"."menus"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
23
|
+
ALTER TABLE "menu_items" ADD CONSTRAINT "menu_items_parent_id_menu_items_id_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."menu_items"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
24
|
+
ALTER TABLE "menus" ADD CONSTRAINT "menus_space_id_spaces_id_fk" FOREIGN KEY ("space_id") REFERENCES "public"."spaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
25
|
+
CREATE INDEX "menu_items_menu_idx" ON "menu_items" USING btree ("menu_id","parent_id","position");--> statement-breakpoint
|
|
26
|
+
CREATE INDEX "menu_items_localization_idx" ON "menu_items" USING btree ("localization_id");--> statement-breakpoint
|
|
27
|
+
-- Every document that was flagged "show in menu" becomes an entry of a "Main" menu in
|
|
28
|
+
-- its space, one entry per document (all translations share it), nested the way the
|
|
29
|
+
-- documents nest in the tree. Entry ids equal the documents' localization ids for the
|
|
30
|
+
-- duration of the migration so parents can be linked in one pass.
|
|
31
|
+
INSERT INTO "menus" ("space_id", "name", "machine_name", "description")
|
|
32
|
+
SELECT "space_id", 'Main', 'main', 'Created from the former "show in menu" flag.'
|
|
33
|
+
FROM "contents" WHERE "visible_in_menu" GROUP BY "space_id";--> statement-breakpoint
|
|
34
|
+
INSERT INTO "menu_items" ("id", "menu_id", "parent_id", "position", "localization_id")
|
|
35
|
+
SELECT DISTINCT ON (c."localization_id") c."localization_id", m."id", NULL, c."position", c."localization_id"
|
|
36
|
+
FROM "contents" c JOIN "menus" m ON m."space_id" = c."space_id" AND m."machine_name" = 'main'
|
|
37
|
+
WHERE c."visible_in_menu"
|
|
38
|
+
ORDER BY c."localization_id", c."locale";--> statement-breakpoint
|
|
39
|
+
UPDATE "menu_items" mi SET "parent_id" = p."localization_id"
|
|
40
|
+
FROM "contents" c JOIN "contents" p ON p."id" = c."parent_id"
|
|
41
|
+
WHERE mi."id" = c."localization_id"
|
|
42
|
+
AND EXISTS (SELECT 1 FROM "menu_items" x WHERE x."id" = p."localization_id" AND x."menu_id" = mi."menu_id");--> statement-breakpoint
|
|
43
|
+
ALTER TABLE "contents" DROP COLUMN "visible_in_menu";--> statement-breakpoint
|
|
44
|
+
ALTER TABLE "published_contents" DROP COLUMN "visible_in_menu";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
CREATE TABLE "roles" (
|
|
2
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
3
|
+
"space_id" uuid NOT NULL,
|
|
4
|
+
"name" text NOT NULL,
|
|
5
|
+
"machine_name" text NOT NULL,
|
|
6
|
+
"description" text,
|
|
7
|
+
"permissions" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
|
8
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
9
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
10
|
+
);
|
|
11
|
+
--> statement-breakpoint
|
|
12
|
+
ALTER TABLE "roles" ADD CONSTRAINT "roles_space_id_spaces_id_fk" FOREIGN KEY ("space_id") REFERENCES "public"."spaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
13
|
+
CREATE UNIQUE INDEX "roles_space_machine_name_key" ON "roles" USING btree ("space_id","machine_name");
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
-- The column was declared by better-auth's key schema and never written; it now holds
|
|
2
|
+
-- the key's grants as a JSON array. Anything that is not one becomes null.
|
|
3
|
+
ALTER TABLE "apikeys" ALTER COLUMN "permissions" SET DATA TYPE jsonb
|
|
4
|
+
USING (CASE WHEN "permissions" ~ '^\s*\[' THEN "permissions"::jsonb ELSE NULL END);
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
CREATE TABLE "push_subscriptions" (
|
|
2
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
3
|
+
"user_id" uuid NOT NULL,
|
|
4
|
+
"endpoint" text NOT NULL,
|
|
5
|
+
"keys" jsonb NOT NULL,
|
|
6
|
+
"user_agent" text,
|
|
7
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
8
|
+
"last_used_at" timestamp with time zone,
|
|
9
|
+
CONSTRAINT "push_subscriptions_endpoint_key" UNIQUE("endpoint")
|
|
10
|
+
);
|
|
11
|
+
--> statement-breakpoint
|
|
12
|
+
CREATE TABLE "workflow_runs" (
|
|
13
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
14
|
+
"workflow_id" uuid NOT NULL,
|
|
15
|
+
"space_id" uuid NOT NULL,
|
|
16
|
+
"trigger" text NOT NULL,
|
|
17
|
+
"status" text DEFAULT 'queued' NOT NULL,
|
|
18
|
+
"context" jsonb NOT NULL,
|
|
19
|
+
"cursor" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
|
20
|
+
"log" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
|
21
|
+
"error" text,
|
|
22
|
+
"resume_at" timestamp with time zone,
|
|
23
|
+
"started_at" timestamp with time zone,
|
|
24
|
+
"finished_at" timestamp with time zone,
|
|
25
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
26
|
+
);
|
|
27
|
+
--> statement-breakpoint
|
|
28
|
+
CREATE TABLE "workflows" (
|
|
29
|
+
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
|
|
30
|
+
"space_id" uuid NOT NULL,
|
|
31
|
+
"name" text NOT NULL,
|
|
32
|
+
"description" text,
|
|
33
|
+
"enabled" boolean DEFAULT false NOT NULL,
|
|
34
|
+
"trigger" jsonb NOT NULL,
|
|
35
|
+
"steps" jsonb DEFAULT '[]'::jsonb NOT NULL,
|
|
36
|
+
"last_scheduled_at" timestamp with time zone,
|
|
37
|
+
"last_run_at" timestamp with time zone,
|
|
38
|
+
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
39
|
+
"updated_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
40
|
+
);
|
|
41
|
+
--> statement-breakpoint
|
|
42
|
+
ALTER TABLE "push_subscriptions" ADD CONSTRAINT "push_subscriptions_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
43
|
+
ALTER TABLE "workflow_runs" ADD CONSTRAINT "workflow_runs_workflow_id_workflows_id_fk" FOREIGN KEY ("workflow_id") REFERENCES "public"."workflows"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
44
|
+
ALTER TABLE "workflow_runs" ADD CONSTRAINT "workflow_runs_space_id_spaces_id_fk" FOREIGN KEY ("space_id") REFERENCES "public"."spaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
45
|
+
ALTER TABLE "workflows" ADD CONSTRAINT "workflows_space_id_spaces_id_fk" FOREIGN KEY ("space_id") REFERENCES "public"."spaces"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
|
|
46
|
+
CREATE INDEX "push_subscriptions_user_idx" ON "push_subscriptions" USING btree ("user_id");--> statement-breakpoint
|
|
47
|
+
CREATE INDEX "workflow_runs_workflow_idx" ON "workflow_runs" USING btree ("workflow_id","created_at");--> statement-breakpoint
|
|
48
|
+
CREATE INDEX "workflow_runs_resume_idx" ON "workflow_runs" USING btree ("status","resume_at");--> statement-breakpoint
|
|
49
|
+
CREATE INDEX "workflows_space_idx" ON "workflows" USING btree ("space_id","enabled");
|