parse-stack-next 5.6.0 → 5.7.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +489 -0
- data/README.md +378 -6
- data/docs/caching.md +748 -0
- data/examples/basic_client.rb +3 -3
- data/examples/basic_server.rb +3 -3
- data/examples/live_query_listener.rb +12 -12
- data/examples/rag_chatbot.rb +14 -14
- data/examples/transaction_example.rb +44 -45
- data/examples/webhook_server.rb +3 -3
- data/lib/parse/access.rb +493 -0
- data/lib/parse/acl_scope.rb +125 -52
- data/lib/parse/agent/approval_gate.rb +0 -0
- data/lib/parse/agent/cancellation_token.rb +3 -3
- data/lib/parse/agent/constraint_translator.rb +4 -4
- data/lib/parse/agent/describe.rb +34 -34
- data/lib/parse/agent/errors.rb +9 -9
- data/lib/parse/agent/mcp_client.rb +61 -59
- data/lib/parse/agent/mcp_dispatcher.rb +89 -101
- data/lib/parse/agent/mcp_rack_app.rb +92 -93
- data/lib/parse/agent/mcp_server.rb +1 -1
- data/lib/parse/agent/mcp_subscriptions.rb +22 -22
- data/lib/parse/agent/metadata_audit.rb +1 -2
- data/lib/parse/agent/metadata_dsl.rb +7 -7
- data/lib/parse/agent/metadata_registry.rb +12 -12
- data/lib/parse/agent/prompt_hardening.rb +4 -4
- data/lib/parse/agent/prompts.rb +10 -10
- data/lib/parse/agent/result_formatter.rb +4 -4
- data/lib/parse/agent/tools.rb +493 -400
- data/lib/parse/agent.rb +157 -116
- data/lib/parse/api/hooks.rb +1 -1
- data/lib/parse/api/server.rb +2 -2
- data/lib/parse/api/users.rb +2 -2
- data/lib/parse/atlas_search/index_manager.rb +1 -1
- data/lib/parse/atlas_search/session.rb +40 -218
- data/lib/parse/atlas_search.rb +181 -60
- data/lib/parse/authorization.rb +466 -0
- data/lib/parse/cache/invalidation.rb +219 -0
- data/lib/parse/cache/keyspace.rb +306 -0
- data/lib/parse/cache/moneta_surface.rb +126 -0
- data/lib/parse/cache/pool.rb +48 -5
- data/lib/parse/cache/redis.rb +425 -10
- data/lib/parse/cache/scoped_view.rb +443 -0
- data/lib/parse/cache/sub_cache.rb +264 -0
- data/lib/parse/cache/upstream_roles.rb +230 -0
- data/lib/parse/client/authentication.rb +1 -1
- data/lib/parse/client/body_builder.rb +1 -2
- data/lib/parse/client/caching.rb +84 -5
- data/lib/parse/client.rb +327 -189
- data/lib/parse/clp_scope.rb +225 -28
- data/lib/parse/console.rb +3 -3
- data/lib/parse/embeddings/batch_embedder.rb +3 -3
- data/lib/parse/embeddings/cache.rb +17 -17
- data/lib/parse/embeddings/cohere.rb +21 -22
- data/lib/parse/embeddings/image_fetch.rb +22 -21
- data/lib/parse/embeddings/jina.rb +21 -21
- data/lib/parse/embeddings/local_http.rb +6 -7
- data/lib/parse/embeddings/media_file.rb +5 -4
- data/lib/parse/embeddings/openai.rb +5 -5
- data/lib/parse/embeddings/provider.rb +1 -1
- data/lib/parse/embeddings/qwen.rb +10 -10
- data/lib/parse/embeddings/spend_cap.rb +2 -3
- data/lib/parse/embeddings/streaming_body.rb +11 -11
- data/lib/parse/embeddings/video_source.rb +7 -6
- data/lib/parse/embeddings/voyage.rb +59 -61
- data/lib/parse/embeddings.rb +28 -27
- data/lib/parse/graphql/type_generator.rb +1 -1
- data/lib/parse/graphql.rb +5 -5
- data/lib/parse/live_query/client.rb +1 -1
- data/lib/parse/live_query.rb +1 -1
- data/lib/parse/lock.rb +15 -18
- data/lib/parse/lock_backend.rb +1 -1
- data/lib/parse/lookup_rewriter.rb +6 -6
- data/lib/parse/model/associations/belongs_to.rb +4 -0
- data/lib/parse/model/associations/collection_proxy.rb +3 -0
- data/lib/parse/model/associations/has_many.rb +4 -0
- data/lib/parse/model/classes/role.rb +227 -28
- data/lib/parse/model/classes/user.rb +60 -1
- data/lib/parse/model/clp.rb +4 -4
- data/lib/parse/model/core/actions.rb +366 -113
- data/lib/parse/model/core/create_lock.rb +0 -2
- data/lib/parse/model/core/describe.rb +57 -57
- data/lib/parse/model/core/embed_managed.rb +21 -23
- data/lib/parse/model/core/fetching.rb +6 -0
- data/lib/parse/model/core/indexing.rb +14 -14
- data/lib/parse/model/core/parse_reference.rb +1 -1
- data/lib/parse/model/core/properties.rb +6 -2
- data/lib/parse/model/core/querying.rb +1 -1
- data/lib/parse/model/core/schema.rb +2 -2
- data/lib/parse/model/core/search_indexing.rb +2 -2
- data/lib/parse/model/core/vector_searchable.rb +4 -5
- data/lib/parse/model/file.rb +21 -15
- data/lib/parse/model/geojson.rb +2 -2
- data/lib/parse/model/geopoint.rb +1 -0
- data/lib/parse/model/object.rb +136 -66
- data/lib/parse/model/pointer.rb +2 -0
- data/lib/parse/model/polygon.rb +3 -6
- data/lib/parse/model/push.rb +2 -2
- data/lib/parse/model/vector.rb +3 -1
- data/lib/parse/mongodb.rb +512 -282
- data/lib/parse/pipeline_security.rb +3 -0
- data/lib/parse/query/constraints.rb +46 -46
- data/lib/parse/query.rb +146 -59
- data/lib/parse/retrieval/agent_tool.rb +34 -20
- data/lib/parse/retrieval/chunk.rb +1 -0
- data/lib/parse/retrieval/reranker/cohere.rb +11 -11
- data/lib/parse/retrieval/reranker.rb +3 -4
- data/lib/parse/retrieval/retriever.rb +4 -6
- data/lib/parse/schema/index_migrator.rb +26 -26
- data/lib/parse/schema/search_index_migrator.rb +19 -19
- data/lib/parse/stack/tasks.rb +6 -6
- data/lib/parse/stack/version.rb +1 -1
- data/lib/parse/stack.rb +10 -10
- data/lib/parse/vector_search/hybrid.rb +66 -41
- data/lib/parse/vector_search.rb +14 -11
- data/lib/parse/webhooks/payload.rb +1 -0
- data/lib/parse/webhooks/registration.rb +4 -4
- data/lib/parse/webhooks/trigger_audit.rb +50 -49
- data/lib/parse/webhooks.rb +120 -5
- data/parse-stack-next.gemspec +6 -6
- metadata +10 -1
|
@@ -51,12 +51,12 @@ module Parse
|
|
|
51
51
|
# inside the save handler (Parse Server has no create trigger); the webhook
|
|
52
52
|
# router runs the destroy chain inside the beforeDelete handler.
|
|
53
53
|
CALLBACK_TRIGGER_MAP = {
|
|
54
|
-
[:save,
|
|
55
|
-
[:create,
|
|
56
|
-
[:save,
|
|
57
|
-
[:create,
|
|
54
|
+
[:save, :before] => :before_save,
|
|
55
|
+
[:create, :before] => :before_save,
|
|
56
|
+
[:save, :after] => :after_save,
|
|
57
|
+
[:create, :after] => :after_save,
|
|
58
58
|
[:destroy, :before] => :before_delete,
|
|
59
|
-
[:destroy, :after]
|
|
59
|
+
[:destroy, :after] => :after_delete,
|
|
60
60
|
}.freeze
|
|
61
61
|
|
|
62
62
|
# ActiveModel callback chains + phases with NO server trigger that can run
|
|
@@ -67,10 +67,10 @@ module Parse
|
|
|
67
67
|
# trigger registration changes that. Surfaced as an informational note, not
|
|
68
68
|
# a fixable gap.
|
|
69
69
|
LOCAL_ONLY_MAP = {
|
|
70
|
-
[:update,
|
|
71
|
-
[:update,
|
|
70
|
+
[:update, :before] => :before_update,
|
|
71
|
+
[:update, :after] => :after_update,
|
|
72
72
|
[:validation, :before] => :before_validation,
|
|
73
|
-
[:validation, :after]
|
|
73
|
+
[:validation, :after] => :after_validation,
|
|
74
74
|
}.freeze
|
|
75
75
|
|
|
76
76
|
# The ActiveModel callback chains we introspect.
|
|
@@ -104,12 +104,12 @@ module Parse
|
|
|
104
104
|
|
|
105
105
|
def initialize(parse_class:, callbacks:, local_routes:, server_triggers:,
|
|
106
106
|
findings:, modeled:)
|
|
107
|
-
@parse_class
|
|
108
|
-
@callbacks
|
|
109
|
-
@local_routes
|
|
107
|
+
@parse_class = parse_class
|
|
108
|
+
@callbacks = callbacks
|
|
109
|
+
@local_routes = local_routes
|
|
110
110
|
@server_triggers = server_triggers
|
|
111
|
-
@findings
|
|
112
|
-
@modeled
|
|
111
|
+
@findings = findings
|
|
112
|
+
@modeled = modeled
|
|
113
113
|
end
|
|
114
114
|
|
|
115
115
|
# @return [Boolean] true when the class has at least one finding.
|
|
@@ -120,12 +120,12 @@ module Parse
|
|
|
120
120
|
# @return [Hash] a JSON-safe representation of this row.
|
|
121
121
|
def to_h
|
|
122
122
|
{
|
|
123
|
-
parse_class:
|
|
124
|
-
modeled:
|
|
125
|
-
callbacks:
|
|
126
|
-
local_routes:
|
|
123
|
+
parse_class: parse_class,
|
|
124
|
+
modeled: modeled,
|
|
125
|
+
callbacks: callbacks,
|
|
126
|
+
local_routes: local_routes,
|
|
127
127
|
server_triggers: server_triggers,
|
|
128
|
-
findings:
|
|
128
|
+
findings: findings,
|
|
129
129
|
}
|
|
130
130
|
end
|
|
131
131
|
end
|
|
@@ -144,11 +144,11 @@ module Parse
|
|
|
144
144
|
# callbacks (e.g. the `_User` default-ACL callback). Off by default to keep
|
|
145
145
|
# the report focused on app-defined logic.
|
|
146
146
|
def initialize(network: true, client: nil, include_framework: false)
|
|
147
|
-
@networked
|
|
147
|
+
@networked = network
|
|
148
148
|
@include_framework = include_framework
|
|
149
|
-
@client
|
|
150
|
-
@server_lookup
|
|
151
|
-
@classes
|
|
149
|
+
@client = client
|
|
150
|
+
@server_lookup = network ? fetch_server_triggers : {}
|
|
151
|
+
@classes = build_classes
|
|
152
152
|
end
|
|
153
153
|
|
|
154
154
|
# @return [Array<Hash>] every finding across all classes, flattened, with the
|
|
@@ -164,10 +164,11 @@ module Parse
|
|
|
164
164
|
def to_h
|
|
165
165
|
{
|
|
166
166
|
networked: networked,
|
|
167
|
-
classes:
|
|
168
|
-
summary:
|
|
167
|
+
classes: @classes.map(&:to_h),
|
|
168
|
+
summary: summary,
|
|
169
169
|
}
|
|
170
170
|
end
|
|
171
|
+
|
|
171
172
|
alias as_json to_h
|
|
172
173
|
|
|
173
174
|
# @return [Hash] finding counts keyed by kind, plus class totals.
|
|
@@ -175,9 +176,9 @@ module Parse
|
|
|
175
176
|
counts = Hash.new(0)
|
|
176
177
|
gaps.each { |g| counts[g[:kind]] += 1 }
|
|
177
178
|
{
|
|
178
|
-
classes_audited:
|
|
179
|
+
classes_audited: @classes.size,
|
|
179
180
|
classes_with_issues: @classes.count(&:issues?),
|
|
180
|
-
findings:
|
|
181
|
+
findings: counts,
|
|
181
182
|
}
|
|
182
183
|
end
|
|
183
184
|
|
|
@@ -212,6 +213,7 @@ module Parse
|
|
|
212
213
|
s[:findings].sort.each { |kind, n| lines << " #{kind}: #{n}" }
|
|
213
214
|
lines.join("\n")
|
|
214
215
|
end
|
|
216
|
+
|
|
215
217
|
alias to_s pretty
|
|
216
218
|
|
|
217
219
|
private
|
|
@@ -231,7 +233,7 @@ module Parse
|
|
|
231
233
|
lookup = Hash.new { |h, k| h[k] = {} }
|
|
232
234
|
client.triggers.results.each do |t|
|
|
233
235
|
next unless t["url"].present?
|
|
234
|
-
name
|
|
236
|
+
name = t["triggerName"]
|
|
235
237
|
klass = t[Parse::Model::KEY_CLASS_NAME] || t["className"]
|
|
236
238
|
next if name.blank? || klass.blank?
|
|
237
239
|
lookup[klass.to_s][name.to_s.underscore.to_sym] = t["url"]
|
|
@@ -262,18 +264,18 @@ module Parse
|
|
|
262
264
|
end
|
|
263
265
|
|
|
264
266
|
def audit_class(name)
|
|
265
|
-
klass
|
|
267
|
+
klass = name == "*" ? nil : (Parse::Model.find_class(name) rescue nil)
|
|
266
268
|
callbacks = klass ? collect_callbacks(klass) : {}
|
|
267
|
-
routes
|
|
268
|
-
server
|
|
269
|
-
findings
|
|
269
|
+
routes = collect_local_routes(name)
|
|
270
|
+
server = @server_lookup[name] || {}
|
|
271
|
+
findings = analyze(name, callbacks, routes, server)
|
|
270
272
|
ClassAudit.new(
|
|
271
|
-
parse_class:
|
|
272
|
-
callbacks:
|
|
273
|
-
local_routes:
|
|
273
|
+
parse_class: name,
|
|
274
|
+
callbacks: callbacks,
|
|
275
|
+
local_routes: routes,
|
|
274
276
|
server_triggers: server,
|
|
275
|
-
findings:
|
|
276
|
-
modeled:
|
|
277
|
+
findings: findings,
|
|
278
|
+
modeled: !klass.nil?,
|
|
277
279
|
)
|
|
278
280
|
end
|
|
279
281
|
|
|
@@ -373,7 +375,7 @@ module Parse
|
|
|
373
375
|
end
|
|
374
376
|
|
|
375
377
|
required.each do |trigger, cb_keys|
|
|
376
|
-
has_route
|
|
378
|
+
has_route = routes.include?(trigger)
|
|
377
379
|
has_server = server.key?(trigger)
|
|
378
380
|
missing = []
|
|
379
381
|
missing << :route unless has_route
|
|
@@ -381,7 +383,7 @@ module Parse
|
|
|
381
383
|
next if missing.empty?
|
|
382
384
|
|
|
383
385
|
findings << {
|
|
384
|
-
kind:
|
|
386
|
+
kind: :callbacks_inert,
|
|
385
387
|
trigger: trigger,
|
|
386
388
|
missing: missing,
|
|
387
389
|
callbacks: cb_keys.sort,
|
|
@@ -398,7 +400,7 @@ module Parse
|
|
|
398
400
|
# Wildcard-only coverage is reported on the "*" row, not here.
|
|
399
401
|
next unless Parse::Webhooks.routes[trigger]&.key?(name)
|
|
400
402
|
findings << {
|
|
401
|
-
kind:
|
|
403
|
+
kind: :route_not_registered,
|
|
402
404
|
trigger: trigger,
|
|
403
405
|
message: "Local `webhook :#{trigger}` block for #{name} is not " \
|
|
404
406
|
"registered as a server trigger — run register_triggers! " \
|
|
@@ -410,7 +412,7 @@ module Parse
|
|
|
410
412
|
server.each_key do |trigger|
|
|
411
413
|
next if routes.include?(trigger)
|
|
412
414
|
findings << {
|
|
413
|
-
kind:
|
|
415
|
+
kind: :orphan_server_trigger,
|
|
414
416
|
trigger: trigger,
|
|
415
417
|
message: "Server trigger #{trigger} is registered for #{name} but no " \
|
|
416
418
|
"local webhook block handles it — every matching operation " \
|
|
@@ -426,7 +428,7 @@ module Parse
|
|
|
426
428
|
end
|
|
427
429
|
if local_only.any?
|
|
428
430
|
findings << {
|
|
429
|
-
kind:
|
|
431
|
+
kind: :local_only_callbacks,
|
|
430
432
|
callbacks: local_only.sort,
|
|
431
433
|
message: "#{name} has local-only callbacks (#{local_only.sort.join(", ")}) " \
|
|
432
434
|
"that no server trigger can run — they fire for Ruby-initiated " \
|
|
@@ -451,25 +453,24 @@ module Parse
|
|
|
451
453
|
|
|
452
454
|
def inert_message(name, trigger, missing, cb_keys)
|
|
453
455
|
callbacks = cb_keys.sort.join(", ")
|
|
454
|
-
reason =
|
|
455
|
-
if missing == [:route, :server]
|
|
456
|
+
reason = if missing == [:route, :server]
|
|
456
457
|
"neither a local `webhook :#{trigger}` block nor a server trigger is " \
|
|
457
|
-
|
|
458
|
+
"registered"
|
|
458
459
|
elsif missing == [:route]
|
|
459
460
|
"no local `webhook :#{trigger}` block is registered to handle it"
|
|
460
461
|
else # [:server]
|
|
461
462
|
"a local block exists but the #{trigger} server trigger is not registered"
|
|
462
463
|
end
|
|
463
464
|
"#{name} callbacks (#{callbacks}) will NOT run for non-Ruby clients: " \
|
|
464
|
-
|
|
465
|
+
"#{reason}. Register `webhook :#{trigger}` and run register_triggers!."
|
|
465
466
|
end
|
|
466
467
|
|
|
467
468
|
def finding_glyph(kind)
|
|
468
469
|
case kind
|
|
469
|
-
when :callbacks_inert
|
|
470
|
-
when :route_not_registered
|
|
470
|
+
when :callbacks_inert then "GAP "
|
|
471
|
+
when :route_not_registered then "GAP "
|
|
471
472
|
when :orphan_server_trigger then "WARN"
|
|
472
|
-
when :local_only_callbacks
|
|
473
|
+
when :local_only_callbacks then "note"
|
|
473
474
|
else " "
|
|
474
475
|
end
|
|
475
476
|
end
|
|
@@ -493,7 +494,7 @@ module Parse
|
|
|
493
494
|
# `pretty: true`.
|
|
494
495
|
def trigger_audit(pretty: false, network: true, client: nil, include_framework: false)
|
|
495
496
|
audit = TriggerAudit.new(
|
|
496
|
-
network: network, client: client, include_framework: include_framework
|
|
497
|
+
network: network, client: client, include_framework: include_framework,
|
|
497
498
|
)
|
|
498
499
|
pretty ? audit.pretty : audit.to_h
|
|
499
500
|
end
|
data/lib/parse/webhooks.rb
CHANGED
|
@@ -132,6 +132,46 @@ module Parse
|
|
|
132
132
|
|
|
133
133
|
class << self
|
|
134
134
|
|
|
135
|
+
# Whether an exception raised by one `after_*` handler prevents the
|
|
136
|
+
# remaining handlers for that same trigger from running.
|
|
137
|
+
#
|
|
138
|
+
# Only the accumulating, non-rejectable `after_*` triggers can have more
|
|
139
|
+
# than one handler (see {Parse::Webhooks::Registration#route}), so this
|
|
140
|
+
# governs `after_save`, `after_delete`, and `after_logout` and nothing
|
|
141
|
+
# else. `before_*` dispatch is untouched: a raise there is how a handler
|
|
142
|
+
# denies an operation, and it must continue to abort.
|
|
143
|
+
#
|
|
144
|
+
# Defaults to `true`, which is the historical behavior. Handlers are
|
|
145
|
+
# folded with `Array#map`, and `map` abandons the collection on the first
|
|
146
|
+
# raise, so a handler that raises silently prevents every handler
|
|
147
|
+
# registered after it from running. That ordering is not something an
|
|
148
|
+
# application fully controls: the SDK's own cache-invalidation triggers
|
|
149
|
+
# install during `Parse.setup` and therefore sit ahead of handlers
|
|
150
|
+
# registered by application files loaded later.
|
|
151
|
+
#
|
|
152
|
+
# Set to `false` to isolate handlers from each other, so that each one
|
|
153
|
+
# runs regardless of what an earlier one raised. The error is reported
|
|
154
|
+
# (a warning plus a `parse.webhooks.handler_error` notification) and
|
|
155
|
+
# dispatch continues.
|
|
156
|
+
#
|
|
157
|
+
# Either way, nothing is reverted. An `after_*` trigger fires once the
|
|
158
|
+
# write has already committed, so there is no version of this setting
|
|
159
|
+
# that can undo the save; the only question it answers is whether the
|
|
160
|
+
# remaining handlers still get to run.
|
|
161
|
+
#
|
|
162
|
+
# @example Keep one failing handler from starving the others
|
|
163
|
+
# Parse::Webhooks.abort_after_callbacks_on_error = false
|
|
164
|
+
#
|
|
165
|
+
# @return [Boolean]
|
|
166
|
+
attr_writer :abort_after_callbacks_on_error
|
|
167
|
+
|
|
168
|
+
# (see #abort_after_callbacks_on_error=)
|
|
169
|
+
# @return [Boolean]
|
|
170
|
+
def abort_after_callbacks_on_error
|
|
171
|
+
return @abort_after_callbacks_on_error unless @abort_after_callbacks_on_error.nil?
|
|
172
|
+
true
|
|
173
|
+
end
|
|
174
|
+
|
|
135
175
|
# Allows support for web frameworks that support auto-reloading of source.
|
|
136
176
|
# @!visibility private
|
|
137
177
|
def reload!(args = {})
|
|
@@ -182,8 +222,26 @@ module Parse
|
|
|
182
222
|
raise ArgumentError, "Invalid Webhook registration trigger #{type} #{className}"
|
|
183
223
|
end
|
|
184
224
|
|
|
185
|
-
#
|
|
186
|
-
|
|
225
|
+
# Triggers whose handlers compose instead of replacing one another.
|
|
226
|
+
#
|
|
227
|
+
# `after_save` / `after_delete` have always accumulated. The remaining
|
|
228
|
+
# `after_*` triggers are added because the SDK itself now registers
|
|
229
|
+
# them for cache invalidation: without this, registering an internal
|
|
230
|
+
# `after_logout` handler would silently replace an application's own,
|
|
231
|
+
# with the winner decided by file load order and no warning.
|
|
232
|
+
#
|
|
233
|
+
# This is safe only for non-rejectable triggers. Parse Server ignores
|
|
234
|
+
# their response body, and {#call_route} normalizes their result to
|
|
235
|
+
# `true` regardless, so `.last` semantics are irrelevant.
|
|
236
|
+
# {REJECTABLE_NON_OBJECT_TRIGGERS} are deliberately excluded: a
|
|
237
|
+
# composite of those must deny if ANY handler denies, and folding with
|
|
238
|
+
# `.last` would discard an earlier rejection.
|
|
239
|
+
composable = type == :after_save || type == :after_delete ||
|
|
240
|
+
(NON_OBJECT_TRIGGERS.include?(type) &&
|
|
241
|
+
!REJECTABLE_NON_OBJECT_TRIGGERS.include?(type) &&
|
|
242
|
+
type.to_s.start_with?("after_"))
|
|
243
|
+
|
|
244
|
+
if composable
|
|
187
245
|
routes[type][className] ||= []
|
|
188
246
|
routes[type][className].push block
|
|
189
247
|
else
|
|
@@ -227,6 +285,60 @@ module Parse
|
|
|
227
285
|
# block that declares a parameter (`do |payload| ... end`) or a splat
|
|
228
286
|
# receives the payload.
|
|
229
287
|
#
|
|
288
|
+
# Run every handler registered for one accumulating `after_*` trigger.
|
|
289
|
+
#
|
|
290
|
+
# `.last` is preserved as the composed result because Parse Server
|
|
291
|
+
# ignores the response body for these triggers and {#call_route}
|
|
292
|
+
# normalizes it anyway, so which handler's value survives is not
|
|
293
|
+
# observable.
|
|
294
|
+
#
|
|
295
|
+
# When {abort_after_callbacks_on_error} is false, a handler that raises
|
|
296
|
+
# is reported and skipped rather than taking the rest of the trigger down
|
|
297
|
+
# with it. Nothing is reverted in either mode: the write these triggers
|
|
298
|
+
# fire on has already committed.
|
|
299
|
+
#
|
|
300
|
+
# @param payload [Parse::Webhooks::Payload] the request payload.
|
|
301
|
+
# @param registry [Array<Proc>] the handlers, in registration order.
|
|
302
|
+
# @param type [Symbol] the trigger being dispatched.
|
|
303
|
+
# @return [Object] the last handler result.
|
|
304
|
+
def dispatch_composed(payload, registry, type)
|
|
305
|
+
return registry.map { |hook| invoke_handler(payload, hook) }.last if
|
|
306
|
+
abort_after_callbacks_on_error
|
|
307
|
+
|
|
308
|
+
last = nil
|
|
309
|
+
registry.each do |hook|
|
|
310
|
+
begin
|
|
311
|
+
last = invoke_handler(payload, hook)
|
|
312
|
+
rescue StandardError => e
|
|
313
|
+
report_handler_error(type, e)
|
|
314
|
+
end
|
|
315
|
+
end
|
|
316
|
+
last
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
# Report a handler failure that was isolated rather than propagated.
|
|
320
|
+
#
|
|
321
|
+
# The message is included because an application's own handler raised it
|
|
322
|
+
# and the application needs it to debug; this is not the SDK's internal
|
|
323
|
+
# `guard`, which deliberately omits messages that can carry a cache key.
|
|
324
|
+
#
|
|
325
|
+
# @param type [Symbol] the trigger being dispatched.
|
|
326
|
+
# @param error [StandardError] the raised error.
|
|
327
|
+
# @return [void]
|
|
328
|
+
def report_handler_error(type, error)
|
|
329
|
+
warn "[Parse::Webhooks] #{type} handler raised #{error.class}: #{error.message}; " \
|
|
330
|
+
"continuing with the remaining handlers " \
|
|
331
|
+
"(Parse::Webhooks.abort_after_callbacks_on_error is false)"
|
|
332
|
+
return unless defined?(ActiveSupport::Notifications)
|
|
333
|
+
begin
|
|
334
|
+
ActiveSupport::Notifications.instrument(
|
|
335
|
+
"parse.webhooks.handler_error", trigger: type, error: error.class.name,
|
|
336
|
+
)
|
|
337
|
+
rescue StandardError
|
|
338
|
+
nil
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
230
342
|
# @param payload [Parse::Webhooks::Payload] the request payload (becomes `self`).
|
|
231
343
|
# @param block [Proc] the registered handler block.
|
|
232
344
|
# @return [Object] the handler's result value.
|
|
@@ -353,14 +465,17 @@ module Parse
|
|
|
353
465
|
if pre_obj.respond_to?(:apply_field_guards!)
|
|
354
466
|
pre_obj.apply_field_guards!(
|
|
355
467
|
master: payload.master? || false,
|
|
356
|
-
is_new: payload.original.blank
|
|
468
|
+
is_new: payload.original.blank?,
|
|
357
469
|
)
|
|
358
470
|
end
|
|
359
471
|
end
|
|
360
472
|
end
|
|
361
473
|
|
|
362
474
|
if registry.is_a?(Array)
|
|
363
|
-
|
|
475
|
+
# An Array registry only ever exists for the accumulating,
|
|
476
|
+
# non-rejectable `after_*` triggers, so isolating handlers here
|
|
477
|
+
# cannot affect `before_*` rejection semantics.
|
|
478
|
+
result = dispatch_composed(payload, registry, type)
|
|
364
479
|
else
|
|
365
480
|
result = invoke_handler(payload, registry)
|
|
366
481
|
end
|
|
@@ -777,7 +892,7 @@ module Parse
|
|
|
777
892
|
else
|
|
778
893
|
if self.logging.present?
|
|
779
894
|
puts "[Webhooks] --> Could not find mapping route for " \
|
|
780
|
-
|
|
895
|
+
"#{Parse::Middleware::BodyBuilder.redact(payload.to_json)}"
|
|
781
896
|
end
|
|
782
897
|
end
|
|
783
898
|
|
data/parse-stack-next.gemspec
CHANGED
|
@@ -6,7 +6,7 @@ require "parse/stack/version"
|
|
|
6
6
|
Gem::Specification.new do |spec|
|
|
7
7
|
spec.name = "parse-stack-next"
|
|
8
8
|
spec.version = Parse::Stack::VERSION
|
|
9
|
-
spec.authors =
|
|
9
|
+
spec.authors = ["Adrian Curtin", "Anthony Persaud", "Henry Spindell"]
|
|
10
10
|
spec.email = ["adrian+parse-stack@neurosynq.net"]
|
|
11
11
|
|
|
12
12
|
spec.summary = %q{Parse Server SDK for Ruby — ORM, queries, auth, and MongoDB-direct access}
|
|
@@ -15,11 +15,11 @@ Gem::Specification.new do |spec|
|
|
|
15
15
|
spec.license = "MIT"
|
|
16
16
|
|
|
17
17
|
spec.metadata = {
|
|
18
|
-
"homepage_uri"
|
|
19
|
-
"source_code_uri"
|
|
20
|
-
"changelog_uri"
|
|
21
|
-
"bug_tracker_uri"
|
|
22
|
-
"documentation_uri"
|
|
18
|
+
"homepage_uri" => "https://github.com/neurosynq/parse-stack-next",
|
|
19
|
+
"source_code_uri" => "https://github.com/neurosynq/parse-stack-next",
|
|
20
|
+
"changelog_uri" => "https://github.com/neurosynq/parse-stack-next/blob/main/CHANGELOG.md",
|
|
21
|
+
"bug_tracker_uri" => "https://github.com/neurosynq/parse-stack-next/issues",
|
|
22
|
+
"documentation_uri" => "https://neurosynq.github.io/parse-stack-next/",
|
|
23
23
|
"rubygems_mfa_required" => "true",
|
|
24
24
|
}
|
|
25
25
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: parse-stack-next
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 5.
|
|
4
|
+
version: 5.7.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Adrian Curtin
|
|
@@ -225,6 +225,7 @@ files:
|
|
|
225
225
|
- docs/_config.yml
|
|
226
226
|
- docs/acl_clp_guide.md
|
|
227
227
|
- docs/atlas_vector_search_guide.md
|
|
228
|
+
- docs/caching.md
|
|
228
229
|
- docs/client_sdk_guide.md
|
|
229
230
|
- docs/mcp_guide.md
|
|
230
231
|
- docs/mongodb_direct_guide.md
|
|
@@ -242,6 +243,7 @@ files:
|
|
|
242
243
|
- examples/webhook_server.rb
|
|
243
244
|
- lib/parse-stack-next.rb
|
|
244
245
|
- lib/parse-stack.rb
|
|
246
|
+
- lib/parse/access.rb
|
|
245
247
|
- lib/parse/acl_scope.rb
|
|
246
248
|
- lib/parse/agent.rb
|
|
247
249
|
- lib/parse/agent/approval_gate.rb
|
|
@@ -284,8 +286,15 @@ files:
|
|
|
284
286
|
- lib/parse/atlas_search/result.rb
|
|
285
287
|
- lib/parse/atlas_search/search_builder.rb
|
|
286
288
|
- lib/parse/atlas_search/session.rb
|
|
289
|
+
- lib/parse/authorization.rb
|
|
290
|
+
- lib/parse/cache/invalidation.rb
|
|
291
|
+
- lib/parse/cache/keyspace.rb
|
|
292
|
+
- lib/parse/cache/moneta_surface.rb
|
|
287
293
|
- lib/parse/cache/pool.rb
|
|
288
294
|
- lib/parse/cache/redis.rb
|
|
295
|
+
- lib/parse/cache/scoped_view.rb
|
|
296
|
+
- lib/parse/cache/sub_cache.rb
|
|
297
|
+
- lib/parse/cache/upstream_roles.rb
|
|
289
298
|
- lib/parse/client.rb
|
|
290
299
|
- lib/parse/client/authentication.rb
|
|
291
300
|
- lib/parse/client/batch.rb
|