quiverdb 0.12.8 → 0.13.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 +34 -7
- package/libs/linux-aarch64/libquiver.so +0 -0
- package/libs/linux-aarch64/libquiver.so.0 +0 -0
- package/libs/linux-aarch64/libquiver_c.so +0 -0
- package/libs/linux-x86_64/libquiver.so +0 -0
- package/libs/linux-x86_64/libquiver.so.0 +0 -0
- package/libs/linux-x86_64/libquiver_c.so +0 -0
- package/libs/macos-aarch64/libquiver.0.dylib +0 -0
- package/libs/macos-aarch64/libquiver_c.dylib +0 -0
- package/libs/windows-x86_64/libquiver.dll +0 -0
- package/libs/windows-x86_64/libquiver_c.dll +0 -0
- package/package.json +1 -1
- package/src/index.ts +2 -2
- package/src/loader.ts +6 -6
- package/src/{lua-api.ts → sandbox-api.ts} +45 -24
- package/src/{lua-runner.ts → sandbox.ts} +7 -7
package/README.md
CHANGED
|
@@ -62,8 +62,9 @@ bun run example.ts
|
|
|
62
62
|
|
|
63
63
|
### Lifecycle
|
|
64
64
|
|
|
65
|
-
- `Database.fromSchema(dbPath, schemaPath)` -- Create database from SQL schema file
|
|
66
|
-
- `Database.fromMigrations(dbPath, migrationsPath)` -- Create database from migrations directory
|
|
65
|
+
- `Database.fromSchema(dbPath, schemaPath, options?)` -- Create database from SQL schema file
|
|
66
|
+
- `Database.fromMigrations(dbPath, migrationsPath, options?)` -- Create database from migrations directory
|
|
67
|
+
- `Database.open(dbPath, options?)` -- Open an existing database file; the schema loads on first use
|
|
67
68
|
- `Database.validateMigrations(migrationsPath)` -- Validate a migrations directory (every `up.sql`, then every `down.sql`, ending with no table left behind) in-memory; throws on failure
|
|
68
69
|
- `close()` -- Close the database connection
|
|
69
70
|
|
|
@@ -74,6 +75,16 @@ bun run example.ts
|
|
|
74
75
|
- `deleteElement(collection, id)` -- Delete element by ID
|
|
75
76
|
- `updateElementByLabel(collection, label, data)` -- Update element by label
|
|
76
77
|
- `deleteElementByLabel(collection, label)` -- Delete element by label
|
|
78
|
+
- `updateRelation(collectionFrom, collectionTo, relationType, id, targetLabel)` -- Point a scalar
|
|
79
|
+
relation at the element labelled `targetLabel` (`null` clears it)
|
|
80
|
+
- `updateRelationByLabel(collectionFrom, collectionTo, relationType, label, targetLabel)` -- Same,
|
|
81
|
+
addressing the element by label
|
|
82
|
+
- `updateVectorGroup(collection, group, id, data)` -- Replace an element's rows in one named vector
|
|
83
|
+
group (`{}` clears it)
|
|
84
|
+
- `updateVectorGroupByLabel(collection, group, label, data)` -- Same, addressing the element by label
|
|
85
|
+
- `updateSetGroup(collection, group, id, data)` -- Replace an element's rows in one named set group
|
|
86
|
+
(`{}` clears it)
|
|
87
|
+
- `updateSetGroupByLabel(collection, group, label, data)` -- Same, addressing the element by label
|
|
77
88
|
|
|
78
89
|
### Read (bulk)
|
|
79
90
|
|
|
@@ -127,6 +138,18 @@ bun run example.ts
|
|
|
127
138
|
|
|
128
139
|
- `readTimeSeriesGroup(collection, group, id)` -- Read time series data for an element
|
|
129
140
|
- `updateTimeSeriesGroup(collection, group, id, data)` -- Write time series data for an element
|
|
141
|
+
(`{}` clears it)
|
|
142
|
+
- `updateTimeSeriesGroupByLabel(collection, group, label, data)` -- Same, addressing the element by
|
|
143
|
+
label
|
|
144
|
+
- `readTimeSeriesRow(collection, group, attribute, dateTime)` -- One value per element: the last
|
|
145
|
+
non-null value at or before `dateTime`, or `null` if there is none
|
|
146
|
+
- `upsertTimeSeriesRow(collection, group, id, row)` -- Insert or replace one row, keyed by its
|
|
147
|
+
dimension value
|
|
148
|
+
- `upsertTimeSeriesRowByLabel(collection, group, label, row)` -- Same, addressing the element by label
|
|
149
|
+
- `hasTimeSeriesFiles(collection)` -- Check whether the collection has a time series files table
|
|
150
|
+
- `listTimeSeriesFilesColumns(collection)` -- List the time series files columns
|
|
151
|
+
- `readTimeSeriesFiles(collection)` -- Read the file paths (`null` for an unset column)
|
|
152
|
+
- `updateTimeSeriesFiles(collection, data)` -- Write the file paths
|
|
130
153
|
|
|
131
154
|
### Query
|
|
132
155
|
|
|
@@ -162,27 +185,31 @@ as an exact INTEGER, a `boolean` as the INTEGER 1 or 0).
|
|
|
162
185
|
|
|
163
186
|
### Introspection
|
|
164
187
|
|
|
165
|
-
- `describe()` --
|
|
188
|
+
- `describe()` -- Whole-database text report (returns a string)
|
|
189
|
+
- `describeCollection(collection)` -- One collection's structure (returns a string)
|
|
190
|
+
- `summarizeCollection(collection)` -- Per-attribute null/value summary (returns a string)
|
|
166
191
|
- `isHealthy()` -- Check database health
|
|
167
192
|
- `path()` -- Get database file path
|
|
168
193
|
- `currentVersion()` -- Get current schema version
|
|
169
194
|
|
|
170
195
|
### Lua
|
|
171
196
|
|
|
172
|
-
- `
|
|
197
|
+
- `Sandbox(db)` -- Create Lua script runner with database access
|
|
173
198
|
- `run(script)` -- Execute a Lua script; returns its return value as a JSON string, or `""` if it
|
|
174
199
|
returned nothing
|
|
175
|
-
- `close()` -- Close the
|
|
200
|
+
- `close()` -- Close the Sandbox
|
|
176
201
|
|
|
177
202
|
## Types
|
|
178
203
|
|
|
179
204
|
Exported types available for TypeScript consumers:
|
|
180
205
|
|
|
181
|
-
- `ScalarValue` -- `number | bigint | string | null`
|
|
182
|
-
- `ArrayValue` -- `number[] | bigint[] | string[]`
|
|
206
|
+
- `ScalarValue` -- `number | bigint | boolean | string | null` (a `boolean` is stored as INTEGER 1/0)
|
|
207
|
+
- `ArrayValue` -- `number[] | bigint[] | boolean[] | string[]`
|
|
183
208
|
- `Value` -- `ScalarValue | ArrayValue`
|
|
184
209
|
- `ElementData` -- `Record<string, Value | undefined>`
|
|
185
210
|
- `QueryParam` -- `number | bigint | boolean | string | null`
|
|
211
|
+
- `GroupColumns` -- `Record<string, (number | bigint | string | boolean | null)[]>`, the column-oriented
|
|
212
|
+
payload of `updateTimeSeriesGroup` / `updateVectorGroup` / `updateSetGroup` (and their `ByLabel` forms)
|
|
186
213
|
- `QuiverError` -- Error class for all Quiver operations
|
|
187
214
|
- `ScalarMetadata` -- Scalar attribute metadata
|
|
188
215
|
- `GroupMetadata` -- Vector/set/time series group metadata
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -12,8 +12,8 @@ export type { CsvOptions } from "./csv.ts";
|
|
|
12
12
|
export { Database } from "./database.ts";
|
|
13
13
|
export { QuiverError } from "./errors.ts";
|
|
14
14
|
export type { GroupColumns, TimeSeriesData } from "./group-columns.ts";
|
|
15
|
-
export {
|
|
16
|
-
export {
|
|
15
|
+
export { SANDBOX_API_REFERENCE } from "./sandbox-api.ts";
|
|
16
|
+
export { Sandbox } from "./sandbox.ts";
|
|
17
17
|
export type { GroupMetadata, ScalarMetadata } from "./metadata.ts";
|
|
18
18
|
export type {
|
|
19
19
|
ArrayValue,
|
package/src/loader.ts
CHANGED
|
@@ -196,11 +196,11 @@ const freeSymbols = {
|
|
|
196
196
|
quiver_database_free_group_metadata_array: { args: [P, USIZE], returns: I32 },
|
|
197
197
|
} as const;
|
|
198
198
|
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
199
|
+
const sandboxSymbols = {
|
|
200
|
+
quiver_sandbox_new: { args: [P, P], returns: I32 },
|
|
201
|
+
quiver_sandbox_free: { args: [P], returns: I32 },
|
|
202
|
+
quiver_sandbox_run: { args: [P, BUF, BUF], returns: I32 },
|
|
203
|
+
quiver_sandbox_free_string: { args: [P], returns: I32 },
|
|
204
204
|
} as const;
|
|
205
205
|
|
|
206
206
|
// Combined symbol map for dlopen.
|
|
@@ -216,7 +216,7 @@ const allSymbols = {
|
|
|
216
216
|
...timeSeriesSymbols,
|
|
217
217
|
...csvSymbols,
|
|
218
218
|
...freeSymbols,
|
|
219
|
-
...
|
|
219
|
+
...sandboxSymbols,
|
|
220
220
|
} as const;
|
|
221
221
|
|
|
222
222
|
// ---------------------------------------------------------------------------
|
|
@@ -1,33 +1,33 @@
|
|
|
1
1
|
// Agent-facing reference for the Lua `db` API available inside run_lua scripts.
|
|
2
2
|
//
|
|
3
|
-
// Authority: `src/
|
|
3
|
+
// Authority: the binders under `src/sandbox/` (`bind_database` through `bind_expression`,
|
|
4
|
+
// this repo) — extracted by hand, NOT imported.
|
|
4
5
|
// The shipped quiverdb native binding is the runtime truth; this is docs.
|
|
5
6
|
//
|
|
6
|
-
// SYNC: `test/
|
|
7
|
+
// SYNC: `test/sandbox-api-sync.test.ts` derives the bound surface from every `.cpp`/`.h` under
|
|
8
|
+
// `src/sandbox/` and checks
|
|
7
9
|
// it AUTOMATICALLY — every `db:`/`quiver.*` name is documented, no documented name has been
|
|
8
10
|
// removed, and the stdlib sentence matches `open_libraries` exactly. What it CANNOT check, and you
|
|
9
11
|
// must still re-diff by hand when the binding changes: arg order, arity, arg types, return shapes,
|
|
10
12
|
// and whether the prose is semantically true.
|
|
11
13
|
//
|
|
12
14
|
// NOTE: the binary/expression subsystems are bound in the native binding and documented below.
|
|
13
|
-
// File-touching operations
|
|
14
|
-
//
|
|
15
|
-
// the quiver.* global.
|
|
15
|
+
// File-touching operations are sandboxed to the database file's directory (the "Filesystem
|
|
16
|
+
// sandbox" bullet lists all of them); the pure-metadata builders stay under the quiver.* global.
|
|
16
17
|
//
|
|
17
18
|
// FORMAT CONVENTION: every db: method appears at least once as the literal token
|
|
18
19
|
// `db:<snake_case_name>`, and every quiver.* function as `quiver.<name>`, so coverage is greppable
|
|
19
20
|
// (the sync test relies on this).
|
|
20
|
-
export const
|
|
21
|
-
# Quiver
|
|
21
|
+
export const SANDBOX_API_REFERENCE = `
|
|
22
|
+
# Quiver Sandbox API Reference
|
|
22
23
|
|
|
23
|
-
Quiver embeds a Lua scripting layer
|
|
24
|
-
|
|
25
|
-
script.
|
|
24
|
+
Quiver embeds a Lua sandbox scripting layer that exposes the same database API as the other language
|
|
25
|
+
bindings. This document is a complete reference of every method available to a Lua script.
|
|
26
26
|
|
|
27
27
|
## Running scripts
|
|
28
28
|
|
|
29
29
|
The database is provided to your script as a global userdata named \`db\`. There are **no
|
|
30
|
-
open/close lifecycle methods in Lua** — the \`
|
|
30
|
+
open/close lifecycle methods in Lua** — the \`Sandbox\` opens the database and hands you \`db\`
|
|
31
31
|
already connected. All methods are called with the colon syntax:
|
|
32
32
|
|
|
33
33
|
\`\`\`lua
|
|
@@ -100,18 +100,25 @@ midnight.
|
|
|
100
100
|
and do not \`pcall\` inside it: a caught error lets the block commit whatever ran.
|
|
101
101
|
- **Standard library.** Loaded standard libraries: base, string, table, math, coroutine, utf8.
|
|
102
102
|
That is the pure-computation set — there is no \`os\`, \`io\`, \`debug\`, or \`package\`/\`require\`,
|
|
103
|
-
and \`dofile\`/\`loadfile\` are removed (string-form \`load\` stays
|
|
104
|
-
the Lua 5.4 \`//\` operator — a language operator,
|
|
103
|
+
and \`dofile\`/\`loadfile\` are removed (string-form \`load\` stays for source text; a precompiled
|
|
104
|
+
binary chunk is refused). Integer division is the Lua 5.4 \`//\` operator — a language operator,
|
|
105
|
+
unrelated to \`math\`. No \`io\` does **not** mean
|
|
105
106
|
a data file on disk is out of reach: read it with \`db:read_csv\` / \`db:read_csv_stream\` (see
|
|
106
107
|
the CSV file reading section below). Never copy, paste, or re-type a data file's contents into
|
|
107
108
|
the script as literals — read the file.
|
|
108
109
|
- **Filesystem sandbox.** Every file-touching operation (\`db:export_csv\`, \`db:import_csv\`,
|
|
109
110
|
\`db:open_file\`, \`db:bin_to_csv\`, \`db:csv_to_bin\`, \`db:validate_migrations\`, \`db:read_csv\`,
|
|
110
|
-
\`db:read_csv_stream\`, \`db:write_csv\`, \`
|
|
111
|
+
\`db:read_csv_stream\`, \`db:write_csv\`, \`save\` on a file or an expression) resolves
|
|
111
112
|
relative paths against the directory containing the database file and rejects anything outside it
|
|
112
113
|
(subdirectories are fine; \`..\` escapes and outside absolute paths throw \`Cannot <op>: path '...' escapes the
|
|
113
114
|
database directory ...\`). On an in-memory database these operations throw
|
|
114
115
|
\`Cannot <op>: database is in-memory, file operations are unavailable\`.
|
|
116
|
+
- **What the sandbox does not limit.** The sandbox controls which files a script can touch and
|
|
117
|
+
which standard libraries exist. It does not bound how much work a script does: there is no
|
|
118
|
+
instruction-count limit, no memory cap and no wall-clock timeout (\`while true do end\` runs
|
|
119
|
+
until the host stops it). Globals persist across \`run()\` calls on the same runner, so a global
|
|
120
|
+
one script sets is visible to the next; use \`local\`. A host that runs untrusted scripts has to
|
|
121
|
+
impose those limits outside the library.
|
|
115
122
|
- **Output.** A script can \`return\` one value and the host receives it as JSON — prefer this over
|
|
116
123
|
\`print()\` when you need structured data back (\`print()\` still works and is captured). Only the
|
|
117
124
|
**first** returned value is encoded. Arrays are 1-indexed. Iterate with \`ipairs\` only where no
|
|
@@ -193,7 +200,7 @@ db:begin_transaction() -- start an explicit transaction
|
|
|
193
200
|
db:commit() -- commit it
|
|
194
201
|
db:rollback() -- roll it back
|
|
195
202
|
db:in_transaction() -- boolean: is a transaction currently open?
|
|
196
|
-
db:transaction(fn) -- run fn(db) inside begin/commit; rollback + rethrow if fn errors
|
|
203
|
+
db:transaction(fn) -- run fn(db) inside begin/commit; rollback + rethrow if fn errors or the commit fails
|
|
197
204
|
\`\`\`
|
|
198
205
|
|
|
199
206
|
To make a group of writes atomic, prefer the \`db:transaction\` wrapper:
|
|
@@ -303,8 +310,13 @@ Notes:
|
|
|
303
310
|
which only the new label resolves. Because the label form delegates to the id form, failures
|
|
304
311
|
that validate the *element* (an empty table, a type mismatch) report
|
|
305
312
|
\`Cannot update_element: ...\`.
|
|
306
|
-
- **
|
|
307
|
-
|
|
313
|
+
- **An empty array clears on update.** On \`update_element\` / \`update_element_by_label\`,
|
|
314
|
+
\`{ col = {} }\` clears the whole group holding \`col\` (all its columns, and every group that
|
|
315
|
+
shares the column name). An empty column beside a non-empty column of the same group throws a
|
|
316
|
+
length error, and a misspelled empty column throws
|
|
317
|
+
\`array '<name>' does not match any vector, set, or time series table ...\`. \`create_element\`
|
|
318
|
+
skips an empty array. To leave a group alone, omit its column: writing back a
|
|
319
|
+
\`read_vectors_by_id\` result clears a group whose read column came back empty or all-NULL.
|
|
308
320
|
- **Arrays must be dense.** A vector/set read returns a NULL cell as a \`nil\` hole, but an element
|
|
309
321
|
array cannot carry one: \`create_element\` / \`update_element\` throw \`array '<name>' has a nil
|
|
310
322
|
hole ...\` rather than cut the array short at the hole. Write NULL cells with
|
|
@@ -712,8 +724,8 @@ no header at all, so \`csv.header\` is absent (\`nil\`, not an empty table) and
|
|
|
712
724
|
file's first line — useful for a file with a junk title row and/or a units row around the real
|
|
713
725
|
header (skip them by naming the header row and slicing \`csv.rows\` in the script). A \`header_row\`
|
|
714
726
|
past the end of the file throws. Passing the separator positionally (\`db:read_csv(path, ";")\`)
|
|
715
|
-
throws \`Cannot read_csv: options must be a table\` instead of silently parsing with
|
|
716
|
-
unknown key, a separator that isn't a single character (or is a quote, CR, LF or NUL — none of
|
|
727
|
+
throws \`Cannot read_csv: options must be a table, got string\` instead of silently parsing with
|
|
728
|
+
a comma; an unknown key, a separator that isn't a single character (or is a quote, CR, LF or NUL — none of
|
|
717
729
|
those can be a delimiter), a non-string option key, or a \`header_row\` that isn't a
|
|
718
730
|
non-negative integer also throws.
|
|
719
731
|
|
|
@@ -870,9 +882,16 @@ File I/O is db-scoped (\`db:open_file\` / \`db:bin_to_csv\` / \`db:csv_to_bin\`)
|
|
|
870
882
|
base paths, sandboxed to the database directory (see Critical rules), and \`get_file_path()\` returns
|
|
871
883
|
the resolved absolute path. The pure-metadata builders and expression constructors live under the
|
|
872
884
|
global \`quiver\` table. Mirrors the Julia surface; aggregation ops are strings (Lua has no enums);
|
|
873
|
-
operators are \`+ - * /\` and unary \`-\`, with scalars allowed on either side. A file
|
|
874
|
-
|
|
875
|
-
|
|
885
|
+
operators are \`+ - * /\` and unary \`-\`, with scalars allowed on either side. A binary file **is** an
|
|
886
|
+
expression: every operator, every \`quiver.*\` expression function and every expression method
|
|
887
|
+
(\`aggregate\`, \`aggregate_agents\`, \`select_agents\`, \`rename_agents\`, \`save\`, \`get_metadata\`) takes
|
|
888
|
+
a file handle directly; \`quiver.expression(f)\` stays as an explicit conversion, and saving from a
|
|
889
|
+
file reads it by path and leaves the handle open. A wrong operand raises \`Cannot <op>: operand must
|
|
890
|
+
be an expression or a binary file, got <type>\`, and extra arguments to a \`quiver.*\` expression
|
|
891
|
+
function (or to an operator metamethod called directly) raise
|
|
892
|
+
\`Cannot <op>: too many arguments (expected N, got M)\`; expression methods ignore extra arguments,
|
|
893
|
+
like every other method. A file handle does not outlive the \`run()\` that opened it: any handle still open when the script returns
|
|
894
|
+
is closed (and a writer flushed), so reopen the file in each script.
|
|
876
895
|
|
|
877
896
|
\`\`\`lua
|
|
878
897
|
local md = quiver.metadata{
|
|
@@ -892,7 +911,9 @@ quiver.metadata_from_toml(text); quiver.metadata_from_element(tbl)
|
|
|
892
911
|
db:bin_to_csv(path) -- aggregate=true by default; pass false to keep time dims as columns
|
|
893
912
|
db:csv_to_bin(path)
|
|
894
913
|
|
|
895
|
-
local e = (
|
|
914
|
+
local e = (r + 10.0) * 2.0 -- a file is an expression; scalars either side
|
|
915
|
+
local per_stage = r:aggregate("stage", "sum") -- every expression method works on a file too
|
|
916
|
+
r:save(copy_path) -- reads the file by path; r stays open
|
|
896
917
|
e = quiver.abs(e); e = quiver.sqrt(e) -- also quiver.log / quiver.exp
|
|
897
918
|
local cond_e = quiver.gt(e, 3.0) -- also quiver.lt/quiver.gte/quiver.lte/quiver.eq/quiver.neq
|
|
898
919
|
-- all -> 1.0/0.0 per element (NaN operand -> NaN)
|
|
@@ -902,7 +923,7 @@ e = e:aggregate("stage", "sum") -- sum/mean/min/max/percent
|
|
|
902
923
|
e = e:aggregate("stage", "percentile", 0.9) -- percentile needs the fraction
|
|
903
924
|
e = e:aggregate_agents("mean") -- collapse the label axis
|
|
904
925
|
e = e:select_agents({"v2"}); e = e:rename_agents({v1 = "alpha"})
|
|
905
|
-
e:save(out_path); e:
|
|
926
|
+
e:save(out_path); e:get_metadata() -- save path is sandboxed like db:open_file
|
|
906
927
|
\`\`\`
|
|
907
928
|
|
|
908
929
|
**\`quiver.metadata{...}\` kwargs and defaults:** \`version\` defaults to \`"1"\`; \`initial_datetime\`
|
|
@@ -4,19 +4,19 @@ import { allocPtrOut, decodeStringFromBuf, readPtrOut, toCString } from "./ffi-h
|
|
|
4
4
|
import type { NativePointer } from "./loader.ts";
|
|
5
5
|
import { getSymbols } from "./loader.ts";
|
|
6
6
|
|
|
7
|
-
export class
|
|
7
|
+
export class Sandbox {
|
|
8
8
|
private _ptr: NativePointer;
|
|
9
9
|
private _closed = false;
|
|
10
10
|
|
|
11
11
|
constructor(db: Database) {
|
|
12
12
|
const lib = getSymbols();
|
|
13
13
|
const outRunner = allocPtrOut();
|
|
14
|
-
check(lib.
|
|
14
|
+
check(lib.quiver_sandbox_new(db._handle, outRunner.buf));
|
|
15
15
|
this._ptr = readPtrOut(outRunner);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
|
19
|
-
* Runs a
|
|
19
|
+
* Runs a script and returns its return value encoded as JSON, or "" if it returned nothing.
|
|
20
20
|
*
|
|
21
21
|
* To execute a script without keeping its writes, wrap the call in
|
|
22
22
|
* `db.beginDryRun()` / `db.endDryRun()`.
|
|
@@ -26,22 +26,22 @@ export class LuaRunner {
|
|
|
26
26
|
const lib = getSymbols();
|
|
27
27
|
const scriptBuf = toCString(script);
|
|
28
28
|
const outResult = allocPtrOut();
|
|
29
|
-
check(lib.
|
|
29
|
+
check(lib.quiver_sandbox_run(this._ptr, scriptBuf.buf, outResult.buf));
|
|
30
30
|
const result = decodeStringFromBuf(outResult);
|
|
31
|
-
lib.
|
|
31
|
+
lib.quiver_sandbox_free_string(readPtrOut(outResult));
|
|
32
32
|
return result;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
close(): void {
|
|
36
36
|
if (this._closed) return;
|
|
37
37
|
const lib = getSymbols();
|
|
38
|
-
check(lib.
|
|
38
|
+
check(lib.quiver_sandbox_free(this._ptr));
|
|
39
39
|
this._closed = true;
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
private ensureOpen(): void {
|
|
43
43
|
if (this._closed) {
|
|
44
|
-
throw new QuiverError("
|
|
44
|
+
throw new QuiverError("Sandbox is closed");
|
|
45
45
|
}
|
|
46
46
|
}
|
|
47
47
|
}
|